Merge "Compose beta bump" into androidx-platform-dev
diff --git a/annotation/annotation-experimental/build.gradle b/annotation/annotation-experimental/build.gradle
index 7608693..0b75e9a 100644
--- a/annotation/annotation-experimental/build.gradle
+++ b/annotation/annotation-experimental/build.gradle
@@ -31,7 +31,6 @@
     name = "Experimental annotation"
     publish = Publish.SNAPSHOT_AND_RELEASE
     mavenVersion = LibraryVersions.ANNOTATION_EXPERIMENTAL
-    mavenMultiplatformVersion = LibraryVersions.ANNOTATION_EXPERIMENTAL_KMP
     inceptionYear = "2019"
     description = "Java annotation for use on unstable Android API surfaces. When used in " +
             "conjunction with the Experimental annotation lint checks, this annotation provides " +
diff --git a/annotation/annotation/build.gradle b/annotation/annotation/build.gradle
index 769ac65..21e996b 100644
--- a/annotation/annotation/build.gradle
+++ b/annotation/annotation/build.gradle
@@ -69,7 +69,6 @@
     name = "Annotation"
     type = LibraryType.PUBLISHED_LIBRARY
     mavenVersion = LibraryVersions.ANNOTATION
-    mavenMultiplatformVersion = LibraryVersions.ANNOTATION_KMP
     inceptionYear = "2013"
     description = "Provides source annotations for tooling and readability."
 }
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
index 484ecac..75b061a 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
@@ -78,35 +78,6 @@
     }
 
     @Test
-    fun withMultiplatformVersion() {
-        val toml = """
-            [versions]
-            V1 = "1.2.3"
-            V1_KMP = "1.2.3-dev05"
-            [groups]
-            G1 = { group = "g.g1", atomicGroupVersion = "versions.V1", multiplatformGroupVersion = "versions.V1_KMP"}
-        """.trimIndent()
-        val dontUseKmpVersions = createLibraryVersionsService(toml)
-        assertThat(
-            dontUseKmpVersions.libraryGroups["G1"]
-        ).isEqualTo(
-            LibraryGroup(
-                group = "g.g1", atomicGroupVersion = Version("1.2.3")
-            )
-        )
-
-        val useKmpVersions =
-            createLibraryVersionsService(toml, useMultiplatformGroupVersions = true)
-        assertThat(
-            useKmpVersions.libraryGroups["G1"]
-        ).isEqualTo(
-            LibraryGroup(
-                group = "g.g1", atomicGroupVersion = Version("1.2.3-dev05")
-            )
-        )
-    }
-
-    @Test
     fun customComposeVersions() {
         val toml = """
             [versions]
@@ -175,68 +146,6 @@
     }
 
     @Test
-    fun missingVersionReference_multiplatform() {
-        val service = createLibraryVersionsService(
-            """
-            [versions]
-            V1 = "1.2.3"
-            [groups]
-            G1 = { group = "g.g1", atomicGroupVersion = "versions.V1", multiplatformGroupVersion = "versions.doesNotExist2" }
-        """.trimIndent()
-        )
-        val result = runCatching {
-            service.libraryGroups["G1"]
-        }
-        assertThat(
-            result.exceptionOrNull()
-        ).hasMessageThat().contains(
-            "Group entry g.g1 specifies doesNotExist2, but such version doesn't exist"
-        )
-    }
-
-    @Test
-    fun malformedVersionReference_multiplatform() {
-        val service = createLibraryVersionsService(
-            """
-            [versions]
-            V1 = "1.2.3"
-            [groups]
-            G1 = { group = "g.g1", atomicGroupVersion = "versions.V1", multiplatformGroupVersion = "V1" }
-        """.trimIndent()
-        )
-        val result = runCatching {
-            service.libraryGroups["G1"]
-        }
-        assertThat(
-            result.exceptionOrNull()
-        ).hasMessageThat().contains(
-            "Group entry multiplatformGroupVersion is expected to start with versions"
-        )
-    }
-
-    @Test
-    fun atomicMultiplatformGroupWithoutAtomicGroup() {
-        val service = createLibraryVersionsService(
-            """
-            [versions]
-            V1 = "1.2.3"
-            [groups]
-            G1 = { group = "g.g1", multiplatformGroupVersion = "versions.V1" }
-            """.trimIndent()
-        )
-        val result = runCatching {
-            service.libraryGroups["G1"]
-        }
-        assertThat(
-            result.exceptionOrNull()
-        ).hasMessageThat().contains(
-            """
-            Cannot specify multiplatformGroupVersion for G1 without specifying an atomicGroupVersion
-            """.trimIndent()
-        )
-    }
-
-    @Test
     fun overrideInclude() {
         val service = createLibraryVersionsService(
             """
@@ -273,7 +182,8 @@
         assertThrows<Exception> {
             service.libraryGroupsByGroupId["g.g1"]
         }.hasMessageThat().contains(
-            "Duplicate library group g.g1 defined in G2 does not set overrideInclude. Declarations beyond the first can only have an effect if they set overrideInclude"
+            "Duplicate library group g.g1 defined in G2 does not set overrideInclude. " +
+                "Declarations beyond the first can only have an effect if they set overrideInclude"
         )
     }
 
@@ -298,118 +208,6 @@
         )
     }
 
-    @Test
-    fun androidxExtension_noAtomicGroup() {
-        runAndroidExtensionTest(
-            projectPath = "myGroup:project1",
-            tomlFile = """
-                [versions]
-                [groups]
-                G1 = { group = "androidx.myGroup" }
-            """.trimIndent(),
-            validateWithKmp = { extension ->
-                extension.mavenVersion = Version("1.0.0")
-                extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
-                assertThat(
-                    extension.mavenGroup
-                ).isEqualTo(
-                    LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
-                )
-                assertThat(
-                    extension.project.version
-                ).isEqualTo(Version("1.0.0-dev01"))
-                extension.validateMavenVersion()
-            },
-            validateWithoutKmp = { extension ->
-                extension.mavenVersion = Version("1.0.0")
-                extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
-                assertThat(
-                    extension.mavenGroup
-                ).isEqualTo(
-                    LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
-                )
-                assertThat(
-                    extension.project.version
-                ).isEqualTo(Version("1.0.0"))
-                extension.validateMavenVersion()
-            }
-        )
-    }
-
-    @Test
-    fun androidxExtension_noAtomicGroup_setKmpVersionFirst() {
-        runAndroidExtensionTest(
-            projectPath = "myGroup:project1",
-            tomlFile = """
-                [versions]
-                [groups]
-                G1 = { group = "androidx.myGroup" }
-            """.trimIndent(),
-            validateWithKmp = { extension ->
-                extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
-                extension.mavenVersion = Version("1.0.0")
-                assertThat(
-                    extension.mavenGroup
-                ).isEqualTo(
-                    LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
-                )
-                assertThat(
-                    extension.project.version
-                ).isEqualTo(Version("1.0.0-dev01"))
-                extension.validateMavenVersion()
-            },
-            validateWithoutKmp = { extension ->
-                extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
-                extension.mavenVersion = Version("1.0.0")
-                assertThat(
-                    extension.mavenGroup
-                ).isEqualTo(
-                    LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
-                )
-                assertThat(
-                    extension.project.version
-                ).isEqualTo(Version("1.0.0"))
-                extension.validateMavenVersion()
-            }
-        )
-    }
-
-    @Test
-    fun androidxExtension_withAtomicGroup() {
-        runAndroidExtensionTest(
-            projectPath = "myGroup:project1",
-            tomlFile = """
-                [versions]
-                V1 = "1.0.0"
-                V1_KMP = "1.0.0-dev01"
-                [groups]
-                G1 = { group = "androidx.myGroup", atomicGroupVersion = "versions.V1", multiplatformGroupVersion = "versions.V1_KMP" }
-            """.trimIndent(),
-            validateWithKmp = { extension ->
-                assertThat(
-                    extension.mavenGroup
-                ).isEqualTo(
-                    LibraryGroup("androidx.myGroup", atomicGroupVersion = Version("1.0.0-dev01"))
-                )
-                assertThat(
-                    extension.project.version
-                ).isEqualTo(Version("1.0.0-dev01"))
-                extension.validateMavenVersion()
-            },
-            validateWithoutKmp = { extension ->
-                assertThat(
-                    extension.mavenGroup
-                ).isEqualTo(
-                    LibraryGroup("androidx.myGroup", atomicGroupVersion = Version("1.0.0"))
-                )
-                assertThat(
-                    extension.project.version
-                ).isEqualTo(Version("1.0.0"))
-                extension.validateMavenVersion()
-            }
-        )
-    }
-
     private fun runAndroidExtensionTest(
         projectPath: String,
         tomlFile: String,
@@ -430,7 +228,6 @@
             createLibraryVersionsService(
                 tomlFileContents = tomlFile,
                 project = rootProject,
-                useMultiplatformGroupVersions = useKmpVersions
             )
             // needed for AndroidXExtension initialization
             rootProject.setSupportRootFolder(rootProjectDir)
@@ -450,7 +247,6 @@
         tomlFileName: String = "libraryversions.toml",
         composeCustomVersion: String? = null,
         composeCustomGroup: String? = null,
-        useMultiplatformGroupVersions: Boolean = false,
         project: Project = ProjectBuilder.builder().withProjectDir(tempDir.newFolder()).build()
     ): LibraryVersionsService {
         val serviceProvider = project.gradle.sharedServices.registerIfAbsent(
@@ -466,9 +262,6 @@
             spec.parameters.composeCustomGroup = project.provider {
                 composeCustomGroup
             }
-            spec.parameters.useMultiplatformGroupVersions = project.provider {
-                useMultiplatformGroupVersions
-            }
         }
         return serviceProvider.get()
     }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 242b964..f0b5bb6 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -70,9 +70,6 @@
             spec.parameters.tomlFileContents = toml
             spec.parameters.composeCustomVersion = composeCustomVersion
             spec.parameters.composeCustomGroup = composeCustomGroup
-            spec.parameters.useMultiplatformGroupVersions = project.provider {
-                Multiplatform.isKotlinNativeEnabled(project)
-            }
         }.get()
         AllLibraryGroups = versionService.libraryGroups.values.toList()
         LibraryVersions = versionService.libraryVersions
@@ -101,32 +98,12 @@
      * Maven version of the library.
      *
      * Note that, setting this is an error if the library group sets an atomic version.
-     * If the build is a multiplatform build, this value will be overridden by
-     * the [mavenMultiplatformVersion] property when it is provided.
-     *
-     * @see mavenMultiplatformVersion
      */
     var mavenVersion: Version? = null
         set(value) {
             field = value
             chooseProjectVersion()
         }
-        get() = if (versionService.useMultiplatformGroupVersions) {
-            mavenMultiplatformVersion ?: field
-        } else {
-            field
-        }
-
-    /**
-     * If set, this will override the [mavenVersion] property in multiplatform builds.
-     *
-     * @see mavenVersion
-     */
-    var mavenMultiplatformVersion: Version? = null
-        set(value) {
-            field = value
-            chooseProjectVersion()
-        }
 
     internal var projectDirectlySpecifiesMavenVersion: Boolean = false
 
@@ -176,12 +153,10 @@
         explanationBuilder: MutableList<String>? = null
     ): LibraryGroup? {
         val overridden = overrideLibraryGroupsByProjectPath.get(projectPath)
-        if (explanationBuilder != null) {
-            explanationBuilder.add(
-                "Library group (in libraryversions.toml) having" +
+        explanationBuilder?.add(
+            "Library group (in libraryversions.toml) having" +
                 " overrideInclude=[\"$projectPath\"] is $overridden"
-            )
-        }
+        )
         if (overridden != null)
             return overridden
 
@@ -206,8 +181,7 @@
         val parentPath = substringBeforeLastColon(projectPath)
 
         if (parentPath == "") {
-            if (explanationBuilder != null)
-                explanationBuilder.add("Parent path for $projectPath is empty")
+            explanationBuilder?.add("Parent path for $projectPath is empty")
             return null
         }
         // convert parent project path to groupId
@@ -218,12 +192,10 @@
         }
 
         // get the library group having that text
-        val result = libraryGroupsByGroupId.get(groupIdText)
-        if (explanationBuilder != null) {
-            explanationBuilder.add(
-                "Library group (in libraryversions.toml) having group=\"$groupIdText\" is $result"
-            )
-        }
+        val result = libraryGroupsByGroupId[groupIdText]
+        explanationBuilder?.add(
+            "Library group (in libraryversions.toml) having group=\"$groupIdText\" is $result"
+        )
         return result
     }
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index bb7940d..f4339cd 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -40,8 +40,6 @@
 import androidx.build.testConfiguration.addAppApkToTestConfigGeneration
 import androidx.build.testConfiguration.configureTestConfigGeneration
 import com.android.build.api.artifact.SingleArtifact
-import com.android.build.api.dsl.ManagedVirtualDevice
-import com.android.build.api.dsl.TestOptions
 import com.android.build.api.variant.ApplicationAndroidComponentsExtension
 import com.android.build.api.variant.HasAndroidTest
 import com.android.build.api.variant.LibraryAndroidComponentsExtension
@@ -100,8 +98,9 @@
  * This plugin reacts to other plugins being added and adds required and optional functionality.
  */
 
-class AndroidXImplPlugin @Inject constructor(val componentFactory: SoftwareComponentFactory) :
-    Plugin<Project> {
+class AndroidXImplPlugin @Inject constructor(
+    private val componentFactory: SoftwareComponentFactory
+) : Plugin<Project> {
     override fun apply(project: Project) {
         if (project.isRoot)
             throw Exception("Root project should use AndroidXRootImplPlugin instead")
@@ -333,7 +332,6 @@
         }
     }
 
-    @Suppress("UnstableApiUsage") // AGP DSL APIs
     private fun configureWithAppPlugin(project: Project, androidXExtension: AndroidXExtension) {
         project.extensions.getByType<AppExtension>().apply {
             configureAndroidBaseOptions(project, androidXExtension)
@@ -374,6 +372,7 @@
         excludeVersionFilesFromTestApks()
     }
 
+    @Suppress("UnstableApiUsage") // usage of experimentalProperties
     private fun Variant.artRewritingWorkaround() {
         // b/279234807
         experimentalProperties.put(
@@ -404,7 +403,7 @@
         }
     }
 
-    fun Project.configureKotlinStdlibVersion() {
+    private fun Project.configureKotlinStdlibVersion() {
         project.configurations.all { configuration ->
             configuration.resolutionStrategy { strategy ->
                 strategy.eachDependency { details ->
@@ -418,7 +417,7 @@
         }
     }
 
-    @Suppress("UnstableApiUsage", "DEPRECATION") // AGP DSL APIs
+    @Suppress("DEPRECATION") // AGP DSL APIs
     private fun configureWithLibraryPlugin(
         project: Project,
         androidXExtension: AndroidXExtension
@@ -600,25 +599,6 @@
         }
     }
 
-    @Suppress("UnstableApiUsage") // Usage of ManagedVirtualDevice
-    private fun TestOptions.configureVirtualDevices() {
-        managedDevices.devices.register<ManagedVirtualDevice>("pixel2api29") {
-            device = "Pixel 2"
-            apiLevel = 29
-            systemImageSource = "aosp"
-        }
-        managedDevices.devices.register<ManagedVirtualDevice>("pixel2api30") {
-            device = "Pixel 2"
-            apiLevel = 30
-            systemImageSource = "aosp"
-        }
-        managedDevices.devices.register<ManagedVirtualDevice>("pixel2api31") {
-            device = "Pixel 2"
-            apiLevel = 31
-            systemImageSource = "aosp"
-        }
-    }
-
     private fun BaseExtension.configureAndroidBaseOptions(
         project: Project,
         androidXExtension: AndroidXExtension
@@ -647,7 +627,6 @@
             // Robolectric 1.7 increased heap size requirements, see b/207169653.
             task.maxHeapSize = "3g"
         }
-        testOptions.configureVirtualDevices()
 
         // Include resources in Robolectric tests as a workaround for b/184641296 and
         // ensure the build directory exists as a workaround for b/187970292.
@@ -869,13 +848,9 @@
             }
 
             // make sure that the project has a group
-            val projectGroup = extension.mavenGroup
-            if (projectGroup == null)
-                return@afterEvaluate
+            val projectGroup = extension.mavenGroup ?: return@afterEvaluate
             // make sure that this group is configured to use a single version
-            val requiredVersion = projectGroup.atomicGroupVersion
-            if (requiredVersion == null)
-                return@afterEvaluate
+            projectGroup.atomicGroupVersion ?: return@afterEvaluate
 
             // We don't want to emit the same constraint into our .module file more than once,
             // and we don't want to try to apply a constraint to a configuration that doesn't accept them,
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index db8882a..417e2d5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -20,6 +20,7 @@
 import org.gradle.api.Action
 import org.gradle.api.NamedDomainObjectCollection
 import org.gradle.api.Project
+import org.gradle.api.provider.Provider
 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
 import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -220,6 +221,6 @@
  * Returns a provider that is set to true if and only if this project has at least 1 kotlin native
  * target (mac, linux, ios).
  */
-internal fun Project.hasKotlinNativeTarget() = project.provider {
+internal fun Project.hasKotlinNativeTarget(): Provider<Boolean> = project.provider {
     project.extensions.getByType(AndroidXMultiplatformExtension::class.java).hasNativeTarget()
-}
\ No newline at end of file
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index 1a36ae6..fd28686 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -47,7 +47,6 @@
 import org.gradle.kotlin.dsl.extra
 
 abstract class AndroidXRootImplPlugin : Plugin<Project> {
-    @Suppress("UnstableApiUsage")
     @get:javax.inject.Inject
     abstract val registry: BuildEventsListenerRegistry
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
index 68fb77c..3ccfbcb 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
@@ -71,6 +71,7 @@
     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()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt b/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt
index d1319ad..e7c6e12 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt
@@ -178,7 +178,7 @@
 
         overrideDirectory?.let {
             val subdirectories = overrideSubdirectories
-            if (subdirectories == null || subdirectories.isEmpty()) return@let
+            if (subdirectories.isNullOrEmpty()) return@let
             subdirectories.map { arguments.add("$it/$InputDir/$IncludedFiles") }
         } ?: arguments.add("$InputDir/$IncludedFiles")
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
index e0b4aa8..d886eca 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
@@ -33,23 +33,19 @@
         var tomlFileContents: Provider<String>
         var composeCustomVersion: Provider<String>
         var composeCustomGroup: Provider<String>
-        var useMultiplatformGroupVersions: Provider<Boolean>
     }
 
     private val parsedTomlFile: TomlParseResult by lazy {
         val result = Toml.parse(parameters.tomlFileContents.get())
         if (result.hasErrors()) {
-            val issues = result.errors().map {
+            val issues = result.errors().joinToString(separator = "\n") {
                 "${parameters.tomlFileName}:${it.position()}: ${it.message}"
-            }.joinToString(separator = "\n")
+            }
             throw Exception("${parameters.tomlFileName} file has issues.\n$issues")
         }
         result
     }
 
-    val useMultiplatformGroupVersions
-        get() = parameters.useMultiplatformGroupVersions.get()
-
     private fun getTable(key: String): TomlTable {
         return parsedTomlFile.getTable(key)
             ?: throw GradleException("Library versions toml file is missing [$key] table")
@@ -78,7 +74,7 @@
     val libraryGroups: Map<String, LibraryGroup> by lazy {
         val result = mutableMapOf<String, LibraryGroup>()
         for (association in libraryGroupAssociations) {
-          result.put(association.declarationName, association.libraryGroup)
+            result[association.declarationName] = association.libraryGroup
         }
         result
     }
@@ -89,9 +85,9 @@
         for (association in libraryGroupAssociations) {
             // Check for duplicate groups
             val groupId = association.libraryGroup.group
-            val existingAssociation = result.get(groupId)
+            val existingAssociation = result[groupId]
             if (existingAssociation != null) {
-                if (association.overrideIncludeInProjectPaths.size < 1) {
+                if (association.overrideIncludeInProjectPaths.isEmpty()) {
                     throw GradleException(
                         "Duplicate library group $groupId defined in " +
                         "${association.declarationName} does not set overrideInclude. " +
@@ -99,7 +95,7 @@
                         "overrideInclude")
                 }
             } else {
-                result.put(groupId, association.libraryGroup)
+                result[groupId] = association.libraryGroup
             }
         }
         result
@@ -110,7 +106,7 @@
        val result = mutableMapOf<String, LibraryGroup>()
        for (association in libraryGroupAssociations) {
            for (overridePath in association.overrideIncludeInProjectPaths) {
-               result.put(overridePath, association.libraryGroup)
+               result[overridePath] = association.libraryGroup
            }
        }
        result
@@ -118,8 +114,6 @@
 
     private val libraryGroupAssociations: List<LibraryGroupAssociation> by lazy {
         val groups = getTable("groups")
-        val useMultiplatformGroupVersion =
-            parameters.useMultiplatformGroupVersions.orElse(false).get()
 
         fun readGroupVersion(groupDefinition: TomlTable, groupName: String, key: String): Version? {
             val versionRef = groupDefinition.getString(key) ?: return null
@@ -154,27 +148,11 @@
                 groupName = groupName,
                 key = AtomicGroupVersion
             )
-            val multiplatformGroupVersion = readGroupVersion(
-                groupDefinition = groupDefinition,
-                groupName = groupName,
-                key = MultiplatformGroupVersion
-            )
-            check(
-                multiplatformGroupVersion == null || atomicGroupVersion != null
-            ) {
-                "Cannot specify $MultiplatformGroupVersion for $name without specifying an " +
-                    AtomicGroupVersion
-            }
-            val groupVersion = when {
-                useMultiplatformGroupVersion -> multiplatformGroupVersion ?: atomicGroupVersion
-                else -> atomicGroupVersion
-            }
-
             val overrideApplyToProjects = (
                 groupDefinition.getArray("overrideInclude")?.toList() ?: listOf()
-            ).map({ it -> it as String })
+            ).map { it as String }
 
-            val group = LibraryGroup(finalGroupName, groupVersion)
+            val group = LibraryGroup(finalGroupName, atomicGroupVersion)
             val association = LibraryGroupAssociation(name, group, overrideApplyToProjects)
             result.add(association)
         }
@@ -194,4 +172,3 @@
 
 private const val VersionReferencePrefix = "versions."
 private const val AtomicGroupVersion = "atomicGroupVersion"
-private const val MultiplatformGroupVersion = "multiplatformGroupVersion"
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ListAndroidXPropertiesTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/ListAndroidXPropertiesTask.kt
index 11abb36..08bb1d7 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ListAndroidXPropertiesTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ListAndroidXPropertiesTask.kt
@@ -24,7 +24,7 @@
  * Lists recognized properties whose names start with "androidx"
  */
 @DisableCachingByDefault(because = "Too many inputs to cache, and runs quickly anyway")
-abstract class ListAndroidXPropertiesTask() : DefaultTask() {
+abstract class ListAndroidXPropertiesTask : DefaultTask() {
     init {
         group = "Help"
         description = "Lists AndroidX-specific properties (specifiable via -Pandroidx.*)"
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt
index 49a1ff3..27076c2 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt
@@ -46,7 +46,7 @@
         group = "Help"
         // compute the output text when the taskgraph is ready so that the output text can be
         // saved in the configuration cache and not generate a configuration cache violation
-        project.gradle.taskGraph.whenReady({ outputText.toString() })
+        project.gradle.taskGraph.whenReady { outputText }
     }
 
     fun setOutput(f: File) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/PrintProjectCoordinatesTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/PrintProjectCoordinatesTask.kt
index 91ea675..03d56e2 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/PrintProjectCoordinatesTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/PrintProjectCoordinatesTask.kt
@@ -54,10 +54,10 @@
     var projectPath: String? = null
 
     @TaskAction
-    public fun printInformation() {
+    fun printInformation() {
         val projectGroup = projectGroup
         val versionFrom =
-            if (projectGroup == null || projectGroup.atomicGroupVersion == null) {
+            if (projectGroup?.atomicGroupVersion == null) {
                 "build.gradle: mavenVersion"
             } else {
                 "group.atomicGroupVersion"
@@ -70,9 +70,9 @@
         // put each component of the explanation on its own line
         groupExplanation.forEachIndexed { i, component ->
             if (i == 0)
-                lines.add(listOf("group   : ${projectGroup?.group} ", "$component"))
+                lines.add(listOf("group   : ${projectGroup?.group} ", component))
             else
-                lines.add(listOf("", "$component"))
+                lines.add(listOf("", component))
         }
         lines.add(listOf("artifact: $projectName ", "(from project name)"))
         lines.add(listOf("version : $version ", "(from $versionFrom)"))
@@ -88,36 +88,32 @@
 
     private fun formatRow(line: List<String>, columnSizes: List<Int>): String {
         var result = ""
-        for (i in 0..(line.size - 1)) {
-            val word = line.get(i)
-            val columnSize = columnSizes.get(i)
+        for (i in line.indices) {
+            val word = line[i]
+            val columnSize = columnSizes[i]
             // only have to pad columns before the last column
-            if (i != line.size - 1)
-                result += word.padEnd(columnSize)
+            result += if (i != line.size - 1)
+                word.padEnd(columnSize)
             else
-                result += word
+                word
         }
         return result
     }
 
     private fun getColumnSizes(lines: List<List<String>>): List<Int> {
-        var maxLengths = mutableListOf<Int>()
+        val maxLengths = mutableListOf<Int>()
         for (line in lines) {
-            for (i in 0..(line.size - 1)) {
-                val word = line.get(i)
+            for (i in line.indices) {
+                val word = line[i]
                 if (maxLengths.size <= i)
                     maxLengths.add(0)
-                if (maxLengths.get(i) < word.length)
-                    maxLengths.set(i, word.length)
+                if (maxLengths[i] < word.length)
+                    maxLengths[i] = word.length
             }
         }
         return maxLengths
     }
 
-    private fun printRow(prefix: String, suffix: String) {
-        println(formatTableLine(prefix, suffix))
-    }
-
     private fun formatTableLine(prefix: String, suffix: String): String {
         return prefix.padEnd(10) + suffix
     }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ProjectParser.kt b/buildSrc/private/src/main/kotlin/androidx/build/ProjectParser.kt
index 9bf82ea..836d7f0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ProjectParser.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ProjectParser.kt
@@ -38,7 +38,7 @@
     private fun parseProject(fileLines: List<String>): ParsedProject {
         var libraryType: String? = null
         var publish: String? = null
-        var specifiesVersion: Boolean = false
+        var specifiesVersion = false
         fileLines.forEach { line ->
             if (libraryType == null)
                 libraryType = line.extractVariableValue(" type = LibraryType.")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ProjectResolver.kt b/buildSrc/private/src/main/kotlin/androidx/build/ProjectResolver.kt
index 65f0428..a28ad23 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ProjectResolver.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ProjectResolver.kt
@@ -21,7 +21,7 @@
 
 // Resolves the given project, and if it is not found,
 // throws an exception that mentions the active project subset, if any (MAIN, COMPOSE, ...)
-public fun Project.resolveProject(projectSpecification: String): Project {
+fun Project.resolveProject(projectSpecification: String): Project {
     try {
         return project.project(projectSpecification)
     } catch (e: UnknownProjectException) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
index d6e9baf..24d1dae 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Release.kt
@@ -244,7 +244,7 @@
             zipTask.dependsOn(verifyInputs)
             zipTask.finalizedBy(verifyOutputs)
             val verifyOutputsTask = verifyOutputs.get()
-            verifyOutputsTask.addFile(zipTask.archiveFile.get().getAsFile())
+            verifyOutputsTask.addFile(zipTask.archiveFile.get().asFile)
         }
     }
 
@@ -371,21 +371,19 @@
     private fun getVerifyProjectZipInputsTask(
         project: Project
     ): TaskProvider<VerifyGMavenZipTask> {
-        val taskProvider = project.tasks.register(
-            "verifyInputs" + PROJECT_ARCHIVE_ZIP_TASK_NAME,
+        return project.tasks.register(
+            "verifyInputs$PROJECT_ARCHIVE_ZIP_TASK_NAME",
             VerifyGMavenZipTask::class.java
         )
-        return taskProvider
     }
 
     private fun getVerifyProjectZipOutputsTask(
         project: Project
     ): TaskProvider<VerifyGMavenZipTask> {
-        val taskProvider = project.tasks.register(
-            "verifyOutputs" + PROJECT_ARCHIVE_ZIP_TASK_NAME,
+        return project.tasks.register(
+            "verifyOutputs$PROJECT_ARCHIVE_ZIP_TASK_NAME",
             VerifyGMavenZipTask::class.java
         )
-        return taskProvider
     }
 }
 
@@ -417,7 +415,7 @@
         val projectSubdir = File("$groupSubdir/${artifact.projectName}")
         val androidxRepoOut = project.getRepositoryDirectory()
         val fromDir = project.file("$androidxRepoOut/$projectSubdir")
-        addFile(File(fromDir, "${artifact.version}"))
+        addFile(File(fromDir, artifact.version))
     }
 
     @TaskAction
@@ -426,7 +424,7 @@
         verifyFiles()
     }
 
-    fun verifySettings() {
+    private fun verifySettings() {
         if (!shouldAddGroupConstraints.get() && !isSnapshotBuild()) {
             throw GradleException(
                 """
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 21b7fdd..6470c1b 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -432,7 +432,6 @@
             GenerateMetadataTask::class.java
         ) { task ->
 
-            @Suppress("UnstableApiUsage") // getResolvedArtifacts() is marked @Incubating
             val artifacts = docsConfiguration.incoming.artifacts.resolvedArtifacts
             task.getArtifactIds().set(
                 artifacts.map { result -> result.map { it.id } }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
index b900386..635f7df 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
@@ -181,7 +181,6 @@
             throw GradleException(message)
         }
         if (source.exists()) {
-            @Suppress("UnstableApiUsage")
             Files.copy(source, dest)
             logger?.lifecycle("Copied $source to $dest")
         } else {
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 3d4d026..ac5aefff 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:Suppress("UnstableApiUsage") // Incubating AGP APIs
-
 package androidx.build.testConfiguration
 
 import androidx.build.AndroidXExtension
@@ -237,6 +235,7 @@
 
             // Recreate the same configuration existing for test modules to pull the artifact
             // from the application module specified in the deviceTests extension.
+            @Suppress("UnstableApiUsage") // Incubating dependencyFactory APIs
             val configuration = configurations.create("${variant.name}TestedApks") { config ->
                 config.isCanBeResolved = true
                 config.isCanBeConsumed = false
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt b/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
index 5d3287f..f91049f 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
@@ -167,7 +167,6 @@
     "androidx.build.license.CheckExternalDependencyLicensesTask_Decorated",
 )
 
-@Suppress("UnstableApiUsage") // usage of BuildService that's incubating
 abstract class TaskUpToDateValidator :
     BuildService<TaskUpToDateValidator.Parameters>, OperationCompletionListener {
     interface Parameters : BuildServiceParameters {
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt b/buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt
index 6064dd2..e2121d7 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/SingleFileCopy.kt
@@ -36,7 +36,6 @@
 
     @TaskAction
     fun copyFile() {
-        @Suppress("UnstableApiUsage")
         Files.copy(sourceFile, destinationFile)
     }
 }
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
index ed428d7..4f5c8ae7 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
@@ -17,16 +17,19 @@
 package androidx.camera.integration.view
 
 import androidx.camera.core.CameraEffect
-import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 
 /**
  * A tone mapping effect for Preview/VideoCapture UseCase.
  */
-internal class ToneMappingSurfaceEffect : CameraEffect(
-    PREVIEW or VIDEO_CAPTURE, mainThreadExecutor(), ToneMappingSurfaceProcessor(), {}
-) {
+internal class ToneMappingSurfaceEffect(
+    targets: Int = PREVIEW or VIDEO_CAPTURE,
+    private val processor: ToneMappingSurfaceProcessor = ToneMappingSurfaceProcessor()
+) :
+    CameraEffect(
+        targets, processor.getGlExecutor(), processor, {}
+    ) {
 
     fun release() {
-        (surfaceProcessor as? ToneMappingSurfaceProcessor)?.release()
+        processor.release()
     }
 }
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
index 9dc5199..33ac70c 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceProcessor.kt
@@ -19,16 +19,17 @@
 import android.graphics.SurfaceTexture
 import android.graphics.SurfaceTexture.OnFrameAvailableListener
 import android.os.Handler
-import android.os.Looper
+import android.os.HandlerThread
 import android.view.Surface
 import androidx.annotation.VisibleForTesting
 import androidx.camera.core.SurfaceOutput
 import androidx.camera.core.SurfaceProcessor
 import androidx.camera.core.SurfaceRequest
-import androidx.camera.core.impl.utils.Threads.checkMainThread
-import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
+import androidx.camera.core.impl.utils.executor.CameraXExecutors.newHandlerExecutor
 import androidx.camera.core.processing.OpenGlRenderer
 import androidx.camera.core.processing.ShaderProvider
+import androidx.core.util.Preconditions.checkState
+import java.util.concurrent.Executor
 
 /**
  * A processor that applies tone mapping on camera output.
@@ -57,28 +58,36 @@
                     """
             }
         }
+
+        private const val GL_THREAD_NAME = "ToneMappingSurfaceProcessor"
     }
 
-    private val mainThreadHandler: Handler = Handler(Looper.getMainLooper())
+    private val glThread: HandlerThread = HandlerThread(GL_THREAD_NAME)
+    private var glHandler: Handler
+    private var glExecutor: Executor
+
+    // Members below are only accessed on GL thread.
     private val glRenderer: OpenGlRenderer = OpenGlRenderer()
     private val outputSurfaces: MutableMap<SurfaceOutput, Surface> = mutableMapOf()
     private val textureTransform: FloatArray = FloatArray(16)
     private val surfaceTransform: FloatArray = FloatArray(16)
     private var isReleased = false
 
-    // For testing.
+    // For testing only
     private var surfaceRequested = false
-    // For testing.
     private var outputSurfaceProvided = false
 
     init {
-        mainThreadExecutor().execute {
+        glThread.start()
+        glHandler = Handler(glThread.looper)
+        glExecutor = newHandlerExecutor(glHandler)
+        glExecutor.execute {
             glRenderer.init(TONE_MAPPING_SHADER_PROVIDER)
         }
     }
 
     override fun onInputSurface(surfaceRequest: SurfaceRequest) {
-        checkMainThread()
+        checkGlThread()
         if (isReleased) {
             surfaceRequest.willNotProvideSurface()
             return
@@ -89,22 +98,22 @@
             surfaceRequest.resolution.width, surfaceRequest.resolution.height
         )
         val surface = Surface(surfaceTexture)
-        surfaceRequest.provideSurface(surface, mainThreadExecutor()) {
+        surfaceRequest.provideSurface(surface, glExecutor) {
             surfaceTexture.setOnFrameAvailableListener(null)
             surfaceTexture.release()
             surface.release()
         }
-        surfaceTexture.setOnFrameAvailableListener(this, mainThreadHandler)
+        surfaceTexture.setOnFrameAvailableListener(this, glHandler)
     }
 
     override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
-        checkMainThread()
+        checkGlThread()
         outputSurfaceProvided = true
         if (isReleased) {
             surfaceOutput.close()
             return
         }
-        val surface = surfaceOutput.getSurface(mainThreadExecutor()) {
+        val surface = surfaceOutput.getSurface(glExecutor) {
             surfaceOutput.close()
             outputSurfaces.remove(surfaceOutput)?.let { removedSurface ->
                 glRenderer.unregisterOutputSurface(removedSurface)
@@ -120,22 +129,35 @@
     }
 
     fun release() {
-        checkMainThread()
-        if (isReleased) {
-            return
+        glExecutor.execute {
+            releaseInternal()
         }
+    }
 
-        // Once release is called, we can stop sending frame to output surfaces.
-        for (surfaceOutput in outputSurfaces.keys) {
-            surfaceOutput.close()
+    private fun releaseInternal() {
+        checkGlThread()
+        if (!isReleased) {
+            // Once release is called, we can stop sending frame to output surfaces.
+            for (surfaceOutput in outputSurfaces.keys) {
+                surfaceOutput.close()
+            }
+            outputSurfaces.clear()
+            glRenderer.release()
+            glThread.quitSafely()
+            isReleased = true
         }
-        outputSurfaces.clear()
-        glRenderer.release()
-        isReleased = true
+    }
+
+    private fun checkGlThread() {
+        checkState(GL_THREAD_NAME == Thread.currentThread().name)
+    }
+
+    fun getGlExecutor(): Executor {
+        return glExecutor
     }
 
     override fun onFrameAvailable(surfaceTexture: SurfaceTexture) {
-        checkMainThread()
+        checkGlThread()
         if (isReleased) {
             return
         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
index e601a24..ae4ae34 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text.selection
 
+import android.os.Build
 import androidx.compose.foundation.text.InternalFoundationTextApi
 import androidx.compose.foundation.text.TEST_FONT_FAMILY
 import androidx.compose.foundation.text.TextDelegate
@@ -513,8 +514,6 @@
         )
     }
 
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getHandlePosition_EndHandle_not_cross_ltr_overflowed() {
         val text = "hello\nworld"
@@ -618,8 +617,6 @@
         )
     }
 
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getHandlePosition_EndHandle_cross_ltr_overflowed() {
         val text = "hello\nworld"
@@ -774,8 +771,6 @@
         )
     }
 
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getHandlePosition_EndHandle_not_cross_rtl_overflowed() {
         val text = "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5"
@@ -826,8 +821,6 @@
         assertThat(coordinates).isEqualTo(Offset(0f, fontSizeInPx))
     }
 
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getHandlePosition_EndHandle_cross_rtl_overflowed() {
         val text = "\u05D0\u05D1\u05D2\n\u05D3\u05D4\u05D5"
@@ -1322,8 +1315,6 @@
         assertThat(lineRange).isEqualTo(TextRange(6, 6))
     }
 
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getRangeOfLineContaining_overflowed_returnsLastVisibleLine() {
         val text = "hello\nworld"
@@ -1425,8 +1416,6 @@
     // start = maxLines 1
     // start = clip
     // start = enabled soft wrap
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getLastVisibleOffset_maxLines1_clip_enabledSoftwrap_multiLineContent() {
         val text = "hello\nworld"
@@ -1452,8 +1441,6 @@
         assertThat(lastVisibleOffset).isEqualTo(5)
     }
 
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getLastVisibleOffset_maxLines1_clip_enabledSoftwrap_singleLineContent() {
         val text = "hello world"
@@ -1481,8 +1468,6 @@
     }
 
     // start = disabled soft wrap
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getLastVisibleOffset_maxLines1_clip_disabledSoftwrap_multiLineContent() {
         val text = "hello\nworld"
@@ -1531,7 +1516,7 @@
 
         val lastVisibleOffset = selectable.getLastVisibleOffset()
 
-        assertThat(lastVisibleOffset).isEqualTo(text.length)
+        assertThat(lastVisibleOffset).isEqualTo(text.length - 1) // ignore last whitespace
     }
 
     // start = ellipsis
@@ -1618,8 +1603,6 @@
         assertThat(lastVisibleOffset).isEqualTo(4)
     }
 
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getLastVisibleOffset_maxLines1_ellipsis_disabledSoftwrap_singleLineContent() {
         val text = "hello world ".repeat(10)
@@ -1643,7 +1626,13 @@
 
         val lastVisibleOffset = selectable.getLastVisibleOffset()
 
-        assertThat(lastVisibleOffset).isEqualTo(19)
+        // the way text layout is calculated with ellipsis is vastly different before and
+        // after API 23. Last visible offset logic cannot be unified below API 23.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            assertThat(lastVisibleOffset).isEqualTo(19)
+        } else {
+            assertThat(lastVisibleOffset).isEqualTo(17)
+        }
     }
 
     // start = height constrained
@@ -1749,7 +1738,7 @@
 
         val lastVisibleOffset = selectable.getLastVisibleOffset()
 
-        assertThat(lastVisibleOffset).isEqualTo(text.length)
+        assertThat(lastVisibleOffset).isEqualTo(text.length - 1) // ignores last whitespace
     }
 
     // start = ellipsis
@@ -1780,8 +1769,6 @@
         assertThat(lastVisibleOffset).isEqualTo(11)
     }
 
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getLastVisibleOffset_limitHeight_ellipsis_enabledSoftwrap_singleLineContent() {
         val text = "hello world ".repeat(10)
@@ -1805,7 +1792,13 @@
 
         val lastVisibleOffset = selectable.getLastVisibleOffset()
 
-        assertThat(lastVisibleOffset).isEqualTo(9)
+        // the way text layout is calculated with ellipsis is vastly different before and
+        // after API 23. Last visible offset logic cannot be unified below API 23.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            assertThat(lastVisibleOffset).isEqualTo(9)
+        } else {
+            assertThat(lastVisibleOffset).isEqualTo(5)
+        }
     }
 
     // start = disabled soft wrap
@@ -1835,8 +1828,6 @@
         assertThat(lastVisibleOffset).isEqualTo(5)
     }
 
-    // TODO(b/270441925); Returns a different result below API 26.
-    @SdkSuppress(minSdkVersion = 26)
     @Test
     fun getLastVisibleOffset_limitHeight_ellipsis_disabledSoftwrap_singleLineContent() {
         val text = "hello world ".repeat(10)
@@ -1860,7 +1851,13 @@
 
         val lastVisibleOffset = selectable.getLastVisibleOffset()
 
-        assertThat(lastVisibleOffset).isEqualTo(19)
+        // the way text layout is calculated with ellipsis is vastly different before and
+        // after API 23. Last visible offset logic cannot be unified below API 23.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            assertThat(lastVisibleOffset).isEqualTo(19)
+        } else {
+            assertThat(lastVisibleOffset).isEqualTo(17)
+        }
     }
 
     @Test
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
index e697e3c..cbb944d 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldTest.kt
@@ -63,8 +63,6 @@
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.toPixelMap
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.NativeKeyEvent
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalFocusManager
@@ -92,11 +90,9 @@
 import androidx.compose.ui.test.isNotFocused
 import androidx.compose.ui.test.junit4.StateRestorationTester
 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.performImeAction
-import androidx.compose.ui.test.performKeyPress
 import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.test.performTextClearance
 import androidx.compose.ui.test.performTextInput
@@ -1244,7 +1240,6 @@
             )
         }
         val textNode = rule.onNodeWithTag(Tag)
-        textNode.performTouchInput { longClick() }
         textNode.performTextInputSelection(TextRange(0, 4))
         textFieldValue.value = ""
 
@@ -1271,7 +1266,6 @@
             )
         }
         val textNode = rule.onNodeWithTag(Tag)
-        textNode.performTouchInput { longClick() }
         textNode.performTextInputSelection(TextRange(2, 8))
         textFieldValue.value = "Hello"
 
@@ -1299,7 +1293,6 @@
             )
         }
         val textNode = rule.onNodeWithTag(Tag)
-        textNode.performTouchInput { longClick() }
         textNode.performTextInputSelection(TextRange(0, 4))
         rule.waitForIdle()
 
@@ -1333,7 +1326,6 @@
             )
         }
         val textNode = rule.onNodeWithTag(Tag)
-        textNode.performTouchInput { longClick() }
         textNode.performTextInputSelection(TextRange(0, 4))
         rule.waitForIdle()
 
@@ -1351,7 +1343,6 @@
         assertThat(actual).isEqualTo(expected)
     }
 
-    @OptIn(ExperimentalTestApi::class)
     @Test
     fun whenSelectedTextIsRemovedByIME_SelectionDoesNotRevert() {
         // hard to find a descriptive name. Take a look at
@@ -1370,24 +1361,7 @@
             )
         }
         val textNode = rule.onNodeWithTag(Tag)
-        textNode.performSemanticsAction(SemanticsActions.RequestFocus)
-        textNode.performTextInputSelection(TextRange(0, 5))
-        textNode.performKeyPress(
-            KeyEvent(
-                NativeKeyEvent(
-                    NativeKeyEvent.ACTION_DOWN,
-                    NativeKeyEvent.KEYCODE_DEL
-                )
-            )
-        )
-        textNode.performKeyPress(
-            KeyEvent(
-                NativeKeyEvent(
-                    NativeKeyEvent.ACTION_UP,
-                    NativeKeyEvent.KEYCODE_DEL
-                )
-            )
-        )
+        textNode.performTextClearance()
 
         rule.waitForIdle()
         textNode.assertTextEquals("")
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
index c95216d..0bae435 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
@@ -133,14 +133,24 @@
 
     var width = widthParameter
         set(value) {
-            field = value
-            drawWithCacheModifierNode.invalidateDrawCache()
+            if (field != value) {
+                field = value
+                drawWithCacheModifierNode.invalidateDrawCache()
+            }
         }
     var brush = brushParameter
+        set(value) {
+            if (field != value) {
+                field = value
+                drawWithCacheModifierNode.invalidateDrawCache()
+            }
+        }
     var shape = shapeParameter
         set(value) {
-            field = value
-            drawWithCacheModifierNode.invalidateDrawCache()
+            if (field != value) {
+                field = value
+                drawWithCacheModifierNode.invalidateDrawCache()
+            }
         }
 
     private val drawWithCacheModifierNode = delegate(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
index e29b20b..25557de 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
@@ -867,12 +867,7 @@
     // If the text hasn't been laid out yet, don't show the modifier.
     val offsetCenter = layoutResult.getBoundingBox(textOffset).center
 
-    val containerCoordinates = manager.state?.layoutCoordinates ?: return Offset.Unspecified
-    val fieldCoordinates =
-        manager.state?.layoutResult?.innerTextFieldCoordinates ?: return Offset.Unspecified
-    val localDragPosition = manager.currentDragPosition?.let {
-        fieldCoordinates.localPositionOf(containerCoordinates, it)
-    } ?: return Offset.Unspecified
+    val localDragPosition = manager.currentDragPosition ?: return Offset.Unspecified
     val dragX = localDragPosition.x
     val line = layoutResult.getLineForOffset(textOffset)
     val lineStartOffset = layoutResult.getLineStart(line)
@@ -899,8 +894,5 @@
         return Offset.Unspecified
     }
 
-    return containerCoordinates.localPositionOf(
-        fieldCoordinates,
-        Offset(centerX, offsetCenter.y)
-    )
+    return Offset(centerX, offsetCenter.y)
 }
\ No newline at end of file
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 614cfda..31c0540 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -209,6 +209,10 @@
     property public abstract long size;
   }
 
+  public sealed interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
+    method public void invalidateDrawCache();
+  }
+
   public final class CacheDrawScope implements androidx.compose.ui.unit.Density {
     method public float getDensity();
     method public float getFontScale();
@@ -236,7 +240,7 @@
   }
 
   public final class DrawModifierKt {
-    method public static androidx.compose.ui.node.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
+    method public static androidx.compose.ui.draw.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
     method public static androidx.compose.ui.Modifier drawBehind(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> onDraw);
     method public static androidx.compose.ui.Modifier drawWithCache(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
     method public static androidx.compose.ui.Modifier drawWithContent(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.ContentDrawScope,kotlin.Unit> onDraw);
@@ -2261,10 +2265,6 @@
 
 package androidx.compose.ui.node {
 
-  public interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
-    method public void invalidateDrawCache();
-  }
-
   public interface CompositionLocalConsumerModifierNode extends androidx.compose.ui.node.DelegatableNode {
   }
 
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 434d4ce..cb0e90b 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -291,6 +291,10 @@
     property public abstract long size;
   }
 
+  public sealed interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
+    method public void invalidateDrawCache();
+  }
+
   public final class CacheDrawScope implements androidx.compose.ui.unit.Density {
     method public float getDensity();
     method public float getFontScale();
@@ -318,7 +322,7 @@
   }
 
   public final class DrawModifierKt {
-    method public static androidx.compose.ui.node.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
+    method public static androidx.compose.ui.draw.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
     method public static androidx.compose.ui.Modifier drawBehind(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> onDraw);
     method public static androidx.compose.ui.Modifier drawWithCache(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
     method public static androidx.compose.ui.Modifier drawWithContent(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.ContentDrawScope,kotlin.Unit> onDraw);
@@ -2472,10 +2476,6 @@
 
 package androidx.compose.ui.node {
 
-  public interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
-    method public void invalidateDrawCache();
-  }
-
   public interface CompositionLocalConsumerModifierNode extends androidx.compose.ui.node.DelegatableNode {
   }
 
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 72c9aaa..1903c49 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -209,6 +209,10 @@
     property public abstract long size;
   }
 
+  public sealed interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
+    method public void invalidateDrawCache();
+  }
+
   public final class CacheDrawScope implements androidx.compose.ui.unit.Density {
     method public float getDensity();
     method public float getFontScale();
@@ -236,7 +240,7 @@
   }
 
   public final class DrawModifierKt {
-    method public static androidx.compose.ui.node.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
+    method public static androidx.compose.ui.draw.CacheDrawModifierNode CacheDrawModifierNode(kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
     method public static androidx.compose.ui.Modifier drawBehind(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> onDraw);
     method public static androidx.compose.ui.Modifier drawWithCache(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.draw.CacheDrawScope,androidx.compose.ui.draw.DrawResult> onBuildDrawCache);
     method public static androidx.compose.ui.Modifier drawWithContent(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.ContentDrawScope,kotlin.Unit> onDraw);
@@ -2268,10 +2272,6 @@
 
 package androidx.compose.ui.node {
 
-  public interface CacheDrawModifierNode extends androidx.compose.ui.node.DrawModifierNode {
-    method public void invalidateDrawCache();
-  }
-
   @kotlin.PublishedApi internal interface ComposeUiNode {
     method public androidx.compose.runtime.CompositionLocalMap getCompositionLocalMap();
     method public androidx.compose.ui.unit.Density getDensity();
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
index 0408729..3c02269 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
@@ -22,7 +22,6 @@
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
-import androidx.compose.ui.node.CacheDrawModifierNode
 import androidx.compose.ui.node.DrawModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.Nodes
@@ -161,6 +160,15 @@
     return CacheDrawModifierNodeImpl(CacheDrawScope(), onBuildDrawCache)
 }
 
+/**
+ * Expands on the [androidx.compose.ui.node.DrawModifierNode] by adding the ability to invalidate
+ * the draw cache for changes in things like shapes and bitmaps (see Modifier.border for a usage
+ * examples).
+ */
+sealed interface CacheDrawModifierNode : DrawModifierNode {
+    fun invalidateDrawCache()
+}
+
 private class CacheDrawModifierNodeImpl(
     private val cacheDrawScope: CacheDrawScope,
     block: CacheDrawScope.() -> DrawResult
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawModifierNode.kt
index 05fb28e..99cd8b2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawModifierNode.kt
@@ -33,15 +33,6 @@
 }
 
 /**
- * Expands on the [androidx.compose.ui.node.DrawModifierNode] by adding the ability to invalidate
- * the draw cache for changes in things like shapes and bitmaps (see Modifier.border for a usage
- * examples).
- */
-interface CacheDrawModifierNode : DrawModifierNode {
-    fun invalidateDrawCache()
-}
-
-/**
  * Invalidates this modifier's draw layer, ensuring that a draw pass will
  * be run on the next frame.
  */
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/PermissionControllerUpsideDownTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/PermissionControllerUpsideDownTest.kt
index 3fd857b..e364fd6 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/PermissionControllerUpsideDownTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/PermissionControllerUpsideDownTest.kt
@@ -20,7 +20,6 @@
 import android.health.connect.HealthPermissions
 import android.os.Build
 import androidx.health.connect.client.PermissionController
-import androidx.health.connect.client.impl.platform.time.SystemDefaultTimeSource
 import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_PREFIX
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,11 +59,10 @@
     fun revokeAllPermissions_revokesHealthPermissions() = runTest {
         val revokedPermissions: MutableList<String> = mutableListOf()
         val permissionController: PermissionController =
-            HealthConnectClientUpsideDownImpl(
-                ApplicationProvider.getApplicationContext(), SystemDefaultTimeSource) {
-                    permissionsToRevoke ->
-                    revokedPermissions.addAll(permissionsToRevoke)
-                }
+            HealthConnectClientUpsideDownImpl(ApplicationProvider.getApplicationContext()) {
+                permissionsToRevoke ->
+                revokedPermissions.addAll(permissionsToRevoke)
+            }
         permissionController.revokeAllPermissions()
         assertThat(revokedPermissions.all { it.startsWith(PERMISSION_PREFIX) }).isTrue()
     }
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt
index ddba2fe..4dcd780 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt
@@ -25,8 +25,6 @@
 import android.health.connect.datatypes.StepsRecord as PlatformStepsRecord
 import android.health.connect.datatypes.WheelchairPushesRecord as PlatformWheelchairPushesRecord
 import android.os.Build
-import androidx.health.connect.client.impl.platform.time.FakeTimeSource
-import androidx.health.connect.client.impl.platform.time.SystemDefaultTimeSource
 import androidx.health.connect.client.records.HeartRateRecord
 import androidx.health.connect.client.records.NutritionRecord
 import androidx.health.connect.client.records.StepsRecord
@@ -67,7 +65,7 @@
                 setOf(DataOrigin("package1"), DataOrigin("package2"))
             )
 
-        with(sdkRequest.toPlatformRequest(SystemDefaultTimeSource)) {
+        with(sdkRequest.toPlatformRequest()) {
             assertThat(recordType).isAssignableTo(PlatformStepsRecord::class.java)
             assertThat(isAscending).isTrue() // Default Order
             assertThat(dataOrigins)
@@ -89,7 +87,7 @@
                 pageToken = "123"
             )
 
-        with(sdkRequest.toPlatformRequest(SystemDefaultTimeSource)) {
+        with(sdkRequest.toPlatformRequest()) {
             assertThat(recordType).isAssignableTo(PlatformStepsRecord::class.java)
             assertThat(pageToken).isEqualTo(123)
             assertThat(dataOrigins)
@@ -105,9 +103,7 @@
         val sdkFilter =
             TimeRangeFilter.between(Instant.ofEpochMilli(123L), Instant.ofEpochMilli(456L))
 
-        with(
-            sdkFilter.toPlatformTimeRangeFilter(SystemDefaultTimeSource) as TimeInstantRangeFilter
-        ) {
+        with(sdkFilter.toPlatformTimeRangeFilter() as TimeInstantRangeFilter) {
             assertThat(endTime).isEqualTo(Instant.ofEpochMilli(456L))
         }
     }
@@ -116,7 +112,7 @@
     fun timeRangeFilter_localDateTime_fromSdkToPlatform() {
         val sdkFilter = TimeRangeFilter.before(LocalDateTime.of(2023, Month.MARCH, 10, 17, 30))
 
-        with(sdkFilter.toPlatformTimeRangeFilter(SystemDefaultTimeSource) as LocalTimeRangeFilter) {
+        with(sdkFilter.toPlatformTimeRangeFilter() as LocalTimeRangeFilter) {
             assertThat(endTime).isEqualTo(LocalDateTime.of(2023, Month.MARCH, 10, 17, 30))
         }
     }
@@ -125,12 +121,9 @@
     fun timeRangeFilter_fromSdkToPlatform_none() {
 
         val sdkFilter = TimeRangeFilter.none()
-        val fakeTimeSource = FakeTimeSource()
-        fakeTimeSource.now = Instant.ofEpochMilli(123L)
 
-        with(sdkFilter.toPlatformTimeRangeFilter(fakeTimeSource) as TimeInstantRangeFilter) {
+        with(sdkFilter.toPlatformTimeRangeFilter() as TimeInstantRangeFilter) {
             assertThat(startTime).isEqualTo(Instant.EPOCH)
-            assertThat(endTime).isEqualTo(fakeTimeSource.now)
         }
     }
 
@@ -165,7 +158,7 @@
                 setOf(DataOrigin("package1"))
             )
 
-        with(sdkRequest.toPlatformRequest(SystemDefaultTimeSource)) {
+        with(sdkRequest.toPlatformRequest()) {
             with(timeRangeFilter as TimeInstantRangeFilter) {
                 assertThat(startTime).isEqualTo(Instant.ofEpochMilli(123L))
                 assertThat(endTime).isEqualTo(Instant.ofEpochMilli(456L))
@@ -190,7 +183,7 @@
                 setOf(DataOrigin("package1"), DataOrigin("package2"))
             )
 
-        with(sdkRequest.toPlatformRequest(SystemDefaultTimeSource)) {
+        with(sdkRequest.toPlatformRequest()) {
             with(timeRangeFilter as TimeInstantRangeFilter) {
                 assertThat(startTime).isEqualTo(Instant.ofEpochMilli(123L))
                 assertThat(endTime).isEqualTo(Instant.ofEpochMilli(456L))
@@ -214,7 +207,7 @@
                 setOf(DataOrigin("package1"), DataOrigin("package2"), DataOrigin("package3"))
             )
 
-        with(sdkRequest.toPlatformRequest(SystemDefaultTimeSource)) {
+        with(sdkRequest.toPlatformRequest()) {
             with(timeRangeFilter as TimeInstantRangeFilter) {
                 assertThat(startTime).isEqualTo(Instant.ofEpochMilli(123L))
                 assertThat(endTime).isEqualTo(Instant.ofEpochMilli(456L))
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/time/FakeTimeSource.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/time/FakeTimeSource.kt
deleted file mode 100644
index 270eaf4..0000000
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/time/FakeTimeSource.kt
+++ /dev/null
@@ -1,26 +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.health.connect.client.impl.platform.time
-
-import android.os.Build
-import androidx.annotation.RequiresApi
-import java.time.Instant
-
-@RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-class FakeTimeSource : TimeSource {
-    override lateinit var now: Instant
-}
\ No newline at end of file
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
index 2086d04..35edb69 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
@@ -43,8 +43,6 @@
 import androidx.health.connect.client.impl.platform.records.toSdkRecord
 import androidx.health.connect.client.impl.platform.records.toSdkResponse
 import androidx.health.connect.client.impl.platform.response.toKtResponse
-import androidx.health.connect.client.impl.platform.time.SystemDefaultTimeSource
-import androidx.health.connect.client.impl.platform.time.TimeSource
 import androidx.health.connect.client.impl.platform.toKtException
 import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_PREFIX
 import androidx.health.connect.client.records.Record
@@ -74,22 +72,17 @@
     private val executor = Dispatchers.Default.asExecutor()
 
     private val context: Context
-    private val timeSource: TimeSource
     private val healthConnectManager: HealthConnectManager
     private val revokePermissionsFunction: (Collection<String>) -> Unit
 
-    constructor(
-        context: Context
-    ) : this(context, SystemDefaultTimeSource, context::revokeSelfPermissionsOnKill)
+    constructor(context: Context) : this(context, context::revokeSelfPermissionsOnKill)
 
     @VisibleForTesting
     internal constructor(
         context: Context,
-        timeSource: TimeSource,
         revokePermissionsFunction: (Collection<String>) -> Unit
     ) {
         this.context = context
-        this.timeSource = timeSource
         this.healthConnectManager =
             context.getSystemService(Context.HEALTHCONNECT_SERVICE) as HealthConnectManager
         this.revokePermissionsFunction = revokePermissionsFunction
@@ -159,7 +152,7 @@
             suspendCancellableCoroutine { continuation ->
                 healthConnectManager.deleteRecords(
                     recordType.toPlatformRecordClass(),
-                    timeRangeFilter.toPlatformTimeRangeFilter(timeSource),
+                    timeRangeFilter.toPlatformTimeRangeFilter(),
                     executor,
                     continuation.asOutcomeReceiver()
                 )
@@ -196,7 +189,7 @@
         val response = wrapPlatformException {
             suspendCancellableCoroutine { continuation ->
                 healthConnectManager.readRecords(
-                    request.toPlatformRequest(timeSource),
+                    request.toPlatformRequest(),
                     executor,
                     continuation.asOutcomeReceiver()
                 )
@@ -212,7 +205,7 @@
         return wrapPlatformException {
                 suspendCancellableCoroutine { continuation ->
                     healthConnectManager.aggregate(
-                        request.toPlatformRequest(timeSource),
+                        request.toPlatformRequest(),
                         executor,
                         continuation.asOutcomeReceiver()
                     )
@@ -227,7 +220,7 @@
         return wrapPlatformException {
                 suspendCancellableCoroutine { continuation ->
                     healthConnectManager.aggregateGroupByDuration(
-                        request.toPlatformRequest(timeSource),
+                        request.toPlatformRequest(),
                         request.timeRangeSlicer,
                         executor,
                         continuation.asOutcomeReceiver()
@@ -243,7 +236,7 @@
         return wrapPlatformException {
                 suspendCancellableCoroutine { continuation ->
                     healthConnectManager.aggregateGroupByPeriod(
-                        request.toPlatformRequest(timeSource),
+                        request.toPlatformRequest(),
                         request.timeRangeSlicer,
                         executor,
                         continuation.asOutcomeReceiver()
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt
index 5c7896d..cce4e13 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt
@@ -30,7 +30,6 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.health.connect.client.aggregate.AggregateMetric
-import androidx.health.connect.client.impl.platform.time.TimeSource
 import androidx.health.connect.client.records.Record
 import androidx.health.connect.client.request.AggregateGroupByDurationRequest
 import androidx.health.connect.client.request.AggregateGroupByPeriodRequest
@@ -39,14 +38,11 @@
 import androidx.health.connect.client.request.ReadRecordsRequest
 import androidx.health.connect.client.time.TimeRangeFilter
 import java.time.Instant
-import java.time.LocalDateTime
-import java.time.ZoneOffset
 
-fun ReadRecordsRequest<out Record>.toPlatformRequest(
-    timeSource: TimeSource
-): ReadRecordsRequestUsingFilters<out PlatformRecord> {
+fun ReadRecordsRequest<out Record>.toPlatformRequest():
+    ReadRecordsRequestUsingFilters<out PlatformRecord> {
     return ReadRecordsRequestUsingFilters.Builder(recordType.toPlatformRecordClass())
-        .setTimeRangeFilter(timeRangeFilter.toPlatformTimeRangeFilter(timeSource))
+        .setTimeRangeFilter(timeRangeFilter.toPlatformTimeRangeFilter())
         .setPageSize(pageSize)
         .apply {
             dataOriginFilter.forEach { addDataOrigins(it.toPlatformDataOrigin()) }
@@ -59,24 +55,14 @@
         .build()
 }
 
-fun TimeRangeFilter.toPlatformTimeRangeFilter(timeSource: TimeSource): PlatformTimeRangeFilter {
-    // TODO(b/272760519): Remove handling for nullable fields in the first two branches. Needed as
-    // the values used in the underlining implementation cause long overflow
+fun TimeRangeFilter.toPlatformTimeRangeFilter(): PlatformTimeRangeFilter {
     return if (startTime != null || endTime != null) {
-        TimeInstantRangeFilter.Builder()
-            .setStartTime(startTime ?: Instant.EPOCH)
-            .setEndTime(endTime ?: timeSource.now)
-            .build()
+        TimeInstantRangeFilter.Builder().setStartTime(startTime).setEndTime(endTime).build()
     } else if (localStartTime != null || localEndTime != null) {
-        LocalTimeRangeFilter.Builder()
-            .setStartTime(localStartTime ?: LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.MIN))
-            .setEndTime(localEndTime ?: LocalDateTime.ofInstant(timeSource.now, ZoneOffset.MAX))
-            .build()
+        LocalTimeRangeFilter.Builder().setStartTime(localStartTime).setEndTime(localEndTime).build()
     } else {
-        TimeInstantRangeFilter.Builder()
-            .setStartTime(Instant.EPOCH)
-            .setEndTime(timeSource.now)
-            .build()
+        // Platform doesn't allow both startTime and endTime to be null
+        TimeInstantRangeFilter.Builder().setStartTime(Instant.EPOCH).build()
     }
 }
 
@@ -89,10 +75,8 @@
         .build()
 }
 
-fun AggregateRequest.toPlatformRequest(timeSource: TimeSource): AggregateRecordsRequest<Any> {
-    return AggregateRecordsRequest.Builder<Any>(
-            timeRangeFilter.toPlatformTimeRangeFilter(timeSource)
-        )
+fun AggregateRequest.toPlatformRequest(): AggregateRecordsRequest<Any> {
+    return AggregateRecordsRequest.Builder<Any>(timeRangeFilter.toPlatformTimeRangeFilter())
         .apply {
             dataOriginFilter.forEach { addDataOriginsFilter(it.toPlatformDataOrigin()) }
             metrics.forEach { addAggregationType(it.toAggregationType()) }
@@ -100,12 +84,8 @@
         .build()
 }
 
-fun AggregateGroupByDurationRequest.toPlatformRequest(
-    timeSource: TimeSource
-): AggregateRecordsRequest<Any> {
-    return AggregateRecordsRequest.Builder<Any>(
-            timeRangeFilter.toPlatformTimeRangeFilter(timeSource)
-        )
+fun AggregateGroupByDurationRequest.toPlatformRequest(): AggregateRecordsRequest<Any> {
+    return AggregateRecordsRequest.Builder<Any>(timeRangeFilter.toPlatformTimeRangeFilter())
         .apply {
             dataOriginFilter.forEach { addDataOriginsFilter(it.toPlatformDataOrigin()) }
             metrics.forEach { addAggregationType(it.toAggregationType()) }
@@ -113,12 +93,8 @@
         .build()
 }
 
-fun AggregateGroupByPeriodRequest.toPlatformRequest(
-    timeSource: TimeSource
-): AggregateRecordsRequest<Any> {
-    return AggregateRecordsRequest.Builder<Any>(
-            timeRangeFilter.toPlatformTimeRangeFilter(timeSource)
-        )
+fun AggregateGroupByPeriodRequest.toPlatformRequest(): AggregateRecordsRequest<Any> {
+    return AggregateRecordsRequest.Builder<Any>(timeRangeFilter.toPlatformTimeRangeFilter())
         .apply {
             dataOriginFilter.forEach { addDataOriginsFilter(it.toPlatformDataOrigin()) }
             metrics.forEach { addAggregationType(it.toAggregationType()) }
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/time/TimeSource.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/time/TimeSource.kt
deleted file mode 100644
index fb3561c..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/time/TimeSource.kt
+++ /dev/null
@@ -1,33 +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:RestrictTo(RestrictTo.Scope.LIBRARY)
-@file:RequiresApi(api = 34)
-
-package androidx.health.connect.client.impl.platform.time
-
-import androidx.annotation.RequiresApi
-import androidx.annotation.RestrictTo
-import java.time.Instant
-
-interface TimeSource {
-    val now: Instant
-}
-
-object SystemDefaultTimeSource : TimeSource {
-    override val now: Instant
-        get() = Instant.now()
-}
diff --git a/mediarouter/mediarouter/api/current.txt b/mediarouter/mediarouter/api/current.txt
index 36663f8..00e0b0ae6 100644
--- a/mediarouter/mediarouter/api/current.txt
+++ b/mediarouter/mediarouter/api/current.txt
@@ -454,7 +454,15 @@
     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_CAR = 9; // 0x9
+    field public static final int DEVICE_TYPE_COMPUTER = 7; // 0x7
+    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_SMARTWATCH = 10; // 0xa
     field 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 PLAYBACK_TYPE_LOCAL = 0; // 0x0
     field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
diff --git a/mediarouter/mediarouter/api/public_plus_experimental_current.txt b/mediarouter/mediarouter/api/public_plus_experimental_current.txt
index 36663f8..00e0b0ae6 100644
--- a/mediarouter/mediarouter/api/public_plus_experimental_current.txt
+++ b/mediarouter/mediarouter/api/public_plus_experimental_current.txt
@@ -454,7 +454,15 @@
     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_CAR = 9; // 0x9
+    field public static final int DEVICE_TYPE_COMPUTER = 7; // 0x7
+    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_SMARTWATCH = 10; // 0xa
     field 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 PLAYBACK_TYPE_LOCAL = 0; // 0x0
     field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
diff --git a/mediarouter/mediarouter/api/restricted_current.txt b/mediarouter/mediarouter/api/restricted_current.txt
index 36663f8..00e0b0ae6 100644
--- a/mediarouter/mediarouter/api/restricted_current.txt
+++ b/mediarouter/mediarouter/api/restricted_current.txt
@@ -454,7 +454,15 @@
     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_CAR = 9; // 0x9
+    field public static final int DEVICE_TYPE_COMPUTER = 7; // 0x7
+    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_SMARTWATCH = 10; // 0xa
     field 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 PLAYBACK_TYPE_LOCAL = 0; // 0x0
     field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteDescriptor.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteDescriptor.java
index a1abac9..e053d4f 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteDescriptor.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteDescriptor.java
@@ -254,10 +254,9 @@
     /**
      * Gets the type of the receiver device associated with this route.
      *
-     * @return The type of the receiver device associated with this route:
-     * {@link MediaRouter.RouteInfo#DEVICE_TYPE_TV} or
-     * {@link MediaRouter.RouteInfo#DEVICE_TYPE_SPEAKER}.
+     * @return The type of the receiver device associated with this route.
      */
+    @MediaRouter.RouteInfo.DeviceType
     public int getDeviceType() {
         return mBundle.getInt(KEY_DEVICE_TYPE);
     }
@@ -730,12 +729,10 @@
         /**
          * Sets the route's receiver device type.
          *
-         * @param deviceType The receive device type of the route:
-         * {@link MediaRouter.RouteInfo#DEVICE_TYPE_TV} or
-         * {@link MediaRouter.RouteInfo#DEVICE_TYPE_SPEAKER}.
+         * @param deviceType The type of the receiver device.
          */
         @NonNull
-        public Builder setDeviceType(int deviceType) {
+        public Builder setDeviceType(@MediaRouter.RouteInfo.DeviceType int deviceType) {
             mBundle.putInt(KEY_DEVICE_TYPE, deviceType);
             return this;
         }
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index aa575786..1e96d25 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -1184,9 +1184,23 @@
          */
         public static final int PLAYBACK_TYPE_REMOTE = 1;
 
-        @IntDef({DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+        @RestrictTo(LIBRARY)
+        @IntDef({
+            DEVICE_TYPE_UNKNOWN,
+            DEVICE_TYPE_TV,
+            DEVICE_TYPE_SPEAKER,
+            DEVICE_TYPE_BLUETOOTH,
+            DEVICE_TYPE_AUDIO_VIDEO_RECEIVER,
+            DEVICE_TYPE_TABLET,
+            DEVICE_TYPE_TABLET_DOCKED,
+            DEVICE_TYPE_COMPUTER,
+            DEVICE_TYPE_GAME_CONSOLE,
+            DEVICE_TYPE_CAR,
+            DEVICE_TYPE_SMARTWATCH,
+            DEVICE_TYPE_GROUP
+        })
         @Retention(RetentionPolicy.SOURCE)
-        private @interface DeviceType {}
+        public @interface DeviceType {}
 
         /**
          * The default receiver device type of the route indicating the type is unknown.
@@ -1221,6 +1235,63 @@
         @RestrictTo(LIBRARY)
         public static final int DEVICE_TYPE_BLUETOOTH = 3;
 
+        /**
+         * A receiver device type indicating that the presentation of the media is happening on an
+         * Audio/Video receiver (AVR).
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_AUDIO_VIDEO_RECEIVER = 4;
+        /**
+         * A receiver device type indicating that the presentation of the media is happening on a
+         * tablet.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_TABLET = 5;
+        /**
+         * A receiver device type indicating that the presentation of the media is happening on a
+         * docked tablet.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_TABLET_DOCKED = 6;
+        /**
+         * A receiver device type indicating that the presentation of the media is happening on a
+         * computer.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_COMPUTER = 7;
+        /**
+         * A receiver device type indicating that the presentation of the media is happening on a
+         * gaming console.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_GAME_CONSOLE = 8;
+        /**
+         * A receiver device type indicating that the presentation of the media is happening on a
+         * car.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_CAR = 9;
+        /**
+         * A receiver device type indicating that the presentation of the media is happening on a
+         * smartwatch.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_SMARTWATCH = 10;
+        /**
+         * A receiver device type indicating that the presentation of the media is happening on a
+         * group of devices.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_GROUP = 1000;
+
         @IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
         @Retention(RetentionPolicy.SOURCE)
         private @interface PlaybackVolume {}
@@ -1622,9 +1693,9 @@
         /**
          * Gets the type of the receiver device associated with this route.
          *
-         * @return The type of the receiver device associated with this route:
-         * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}.
+         * @return The type of the receiver device associated with this route.
          */
+        @DeviceType
         public int getDeviceType() {
             return mDeviceType;
         }
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
index 2648406..74c12ed 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
@@ -16,6 +16,7 @@
 
 package androidx.mediarouter.media;
 
+import android.annotation.SuppressLint;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -862,6 +863,7 @@
             super(context, syncCallback);
         }
 
+        @SuppressLint("WrongConstant") // False positive. See b/283059575.
         @Override
         @DoNotInline
         protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
index 68778460..9670896 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
@@ -95,6 +95,11 @@
         var isSessionOpened = false
         var internalClient: SandboxedUiAdapter.SessionClient? = null
         var testSession: TestSession? = null
+        var isZOrderOnTop = true
+
+        // When set to true, the onSessionOpened callback will only be invoked when specified
+        // by the test. This is to test race conditions when the session is being loaded.
+        var delayOpenSessionCallback = false
 
         override fun openSession(
             context: Context,
@@ -107,11 +112,18 @@
             internalClient = client
             testSession =
                 TestSession(context, initialWidth, initialHeight, resizeLatch, configChangedLatch)
-            client.onSessionOpened(testSession!!)
+            if (!delayOpenSessionCallback) {
+                client.onSessionOpened(testSession!!)
+            }
             isSessionOpened = true
+            this.isZOrderOnTop = isZOrderOnTop
             openSessionLatch?.countDown()
         }
 
+        internal fun sendOnSessionOpened() {
+            internalClient?.onSessionOpened(testSession!!)
+        }
+
         inner class TestSession(
             context: Context,
             initialWidth: Int,
@@ -120,7 +132,7 @@
             private var configChangedLatch: CountDownLatch?
         ) : SandboxedUiAdapter.Session {
 
-            var isZOrderChanged = false
+            var zOrderChangedLatch: CountDownLatch = CountDownLatch(1)
 
             override val view: View = View(context)
 
@@ -137,7 +149,8 @@
             }
 
             override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
-                isZOrderChanged = true
+                [email protected] = isZOrderOnTop
+                zOrderChangedLatch.countDown()
             }
 
             override fun notifyConfigurationChanged(configuration: Configuration) {
@@ -225,11 +238,7 @@
 
     @Test
     fun onAttachedToWindowTest() {
-        activity.runOnUiThread(Runnable {
-            activity.findViewById<LinearLayout>(
-                R.id.mainlayout
-            ).addView(view)
-        })
+        addViewToLayout()
         openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
         assertTrue(testSandboxedUiAdapter.isSessionOpened)
         assertTrue(view.childCount == 1)
@@ -240,11 +249,7 @@
     fun childViewRemovedOnErrorTest() {
         assertTrue(view.childCount == 0)
 
-        activity.runOnUiThread(Runnable {
-            activity.findViewById<LinearLayout>(
-                R.id.mainlayout
-            ).addView(view)
-        })
+        addViewToLayout()
 
         openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
         assertTrue(openSessionLatch.count == 0.toLong())
@@ -259,32 +264,78 @@
 
     @Test
     fun onZOrderChangedTest() {
-        val layout = activity.findViewById<LinearLayout>(
-            R.id.mainlayout
-        )
+        addViewToLayout()
 
-        view.setZOrderOnTopAndEnableUserInteraction(true)
-        resizeLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
-        testSandboxedUiAdapter.testSession?.let { assertFalse(it.isZOrderChanged) }
+        // When session is opened, the provider should not receive a Z-order notification.
+        assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        val session = testSandboxedUiAdapter.testSession!!
+        val adapter = testSandboxedUiAdapter
+        assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+        assertThat(adapter.isZOrderOnTop).isTrue()
 
-        activity.runOnUiThread(Runnable {
-            layout.addView(view)
-        })
+        // When state changes to false, the provider should be notified.
         view.setZOrderOnTopAndEnableUserInteraction(false)
+        assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        assertThat(adapter.isZOrderOnTop).isFalse()
 
-        resizeLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
-        testSandboxedUiAdapter.testSession?.let { assertTrue(it.isZOrderChanged) }
-        assertTrue(resizeLatch.count == 0.toLong())
+        // When state changes back to true, the provider should be notified.
+        session.zOrderChangedLatch = CountDownLatch(1)
+        view.setZOrderOnTopAndEnableUserInteraction(true)
+        assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        assertThat(adapter.isZOrderOnTop).isTrue()
+    }
+
+    @Test
+    fun onZOrderUnchangedTest() {
+        addViewToLayout()
+
+        // When session is opened, the provider should not receive a Z-order notification.
+        assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        val session = testSandboxedUiAdapter.testSession!!
+        val adapter = testSandboxedUiAdapter
+        assertThat(adapter.isZOrderOnTop).isTrue()
+
+        // When Z-order state is unchanged, the provider should not be notified.
+        session.zOrderChangedLatch = CountDownLatch(1)
+        view.setZOrderOnTopAndEnableUserInteraction(true)
+        assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+        assertThat(adapter.isZOrderOnTop).isTrue()
+    }
+
+    @Test
+    fun setZOrderNotOnTopBeforeOpeningSession() {
+        view.setZOrderOnTopAndEnableUserInteraction(false)
+        addViewToLayout()
+        assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        val session = testSandboxedUiAdapter.testSession!!
+
+        // The initial Z-order state is passed to the session, but notifyZOrderChanged is not called
+        assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+        assertThat(testSandboxedUiAdapter.isZOrderOnTop).isFalse()
+    }
+
+    @Test
+    fun setZOrderNotOnTopWhileSessionLoading() {
+        testSandboxedUiAdapter.delayOpenSessionCallback = true
+        addViewToLayout()
+        assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        view.setZOrderOnTopAndEnableUserInteraction(false)
+        val session = testSandboxedUiAdapter.testSession!!
+        assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+        activity.runOnUiThread {
+            testSandboxedUiAdapter.sendOnSessionOpened()
+        }
+
+        // After session has opened, the pending Z order changed made while loading is notified
+        // th the session.
+        assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        assertThat(testSandboxedUiAdapter.isZOrderOnTop).isFalse()
     }
 
     @Test
     @Ignore("b/272324246")
     fun onConfigurationChangedTest() {
-        val layout = activity.findViewById<LinearLayout>(R.id.mainlayout)
-
-        activity.runOnUiThread(Runnable {
-            layout.addView(view)
-        })
+        addViewToLayout()
 
         openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
         assertTrue(openSessionLatch.count == 0.toLong())
@@ -297,16 +348,12 @@
 
     @Test
     fun onSizeChangedTest() {
-        val layout = activity.findViewById<LinearLayout>(
-            R.id.mainlayout
-        )
-
-        activity.runOnUiThread(Runnable {
-            layout.addView(view)
+        addViewToLayout()
+        assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        activity.runOnUiThread {
             view.layoutParams = LinearLayout.LayoutParams(100, 200)
-        })
-        resizeLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
-        assertTrue(resizeLatch.count == 0.toLong())
+        }
+        assertThat(resizeLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
         assertTrue(view.width == 100 && view.height == 200)
     }
 
@@ -356,4 +403,12 @@
             "XML overrides SandboxedSdkView.isTransitionGroup", view.isTransitionGroup
         )
     }
+
+    private fun addViewToLayout() {
+        activity.runOnUiThread {
+            activity.findViewById<LinearLayout>(
+                R.id.mainlayout
+            ).addView(view)
+        }
+    }
 }
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
index 8cb8485..7842b0a 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
@@ -124,11 +124,14 @@
         checkClientOpenSession()
     }
 
+    /**
+     * Sets the Z-ordering of the [SandboxedSdkView]'s surface, relative to its window.
+     */
     fun setZOrderOnTopAndEnableUserInteraction(setOnTop: Boolean) {
         if (setOnTop == isZOrderOnTop) return
-        this.isZOrderOnTop = setOnTop
-        checkClientOpenSession()
         client?.notifyZOrderChanged(setOnTop)
+        isZOrderOnTop = setOnTop
+        checkClientOpenSession()
     }
 
     private fun checkClientOpenSession() {
@@ -251,16 +254,16 @@
         oldHeight: Int
     ) {
         super.onSizeChanged(width, height, oldWidth, oldHeight)
-        checkClientOpenSession()
         client?.notifyResized(width, height)
+        checkClientOpenSession()
     }
 
     // TODO(b/270971893) Compare to old configuration before notifying of configuration change.
     override fun onConfigurationChanged(config: Configuration?) {
         requireNotNull(config) { "Config cannot be null" }
         super.onConfigurationChanged(config)
-        checkClientOpenSession()
         client?.notifyConfigurationChanged(config)
+        checkClientOpenSession()
     }
 
     /**
@@ -330,9 +333,7 @@
         private var pendingWidth: Int? = null
         private var pendingHeight: Int? = null
 
-        // pendingZOrderOnTop ensures visible and interactive provider UI as long as the UI is
-        // unobstructed to the user.
-        private var pendingZOrderOnTop: Boolean? = true
+        private var pendingZOrderOnTop: Boolean? = null
         private var pendingConfiguration: Configuration? = null
 
         fun notifyConfigurationChanged(configuration: Configuration) {
@@ -390,6 +391,7 @@
             pendingZOrderOnTop?.let {
                 session.notifyZOrderChanged(it)
             }
+            pendingZOrderOnTop = null
         }
 
         override fun onSessionError(throwable: Throwable) {
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleDynamicGroupMediaRouteProvider.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleDynamicGroupMediaRouteProvider.java
index 3ca2448..47ffed2 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleDynamicGroupMediaRouteProvider.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/providers/SampleDynamicGroupMediaRouteProvider.java
@@ -345,7 +345,7 @@
 
             mDynamicRouteDescriptors.put(routeId, builder.build());
 
-            if (routeDescriptor.getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_TV) {
+            if (routeDescriptor.getDeviceType() == RouteInfo.DEVICE_TYPE_TV) {
                 mTvSelectedCount++;
             }
             return true;
@@ -365,7 +365,7 @@
 
             MediaRouteDescriptor routeDescriptor =
                     mRouteDescriptors.get(dynamicDescriptor.getRouteDescriptor().getId());
-            if (routeDescriptor.getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_TV) {
+            if (routeDescriptor.getDeviceType() == RouteInfo.DEVICE_TYPE_TV) {
                 mTvSelectedCount--;
             }
             return true;
@@ -532,7 +532,7 @@
 
         private int countTvFromRoute(MediaRouteDescriptor routeDescriptor) {
             if (routeDescriptor.getGroupMemberIds().isEmpty()) {
-                return (routeDescriptor.getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_TV)
+                return (routeDescriptor.getDeviceType() == RouteInfo.DEVICE_TYPE_TV)
                         ? 1 : 0;
             }
             int count = 0;
diff --git a/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt b/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt
new file mode 100644
index 0000000..f80b15a
--- /dev/null
+++ b/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.text.android
+
+import android.graphics.Typeface
+import android.os.Build
+import android.text.TextPaint
+import android.text.TextUtils
+import androidx.core.content.res.ResourcesCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.testutils.fonts.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(InternalPlatformTextApi::class)
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class TextLayoutLineVisibleEndTest {
+    lateinit var sampleTypeface: Typeface
+    @Before
+    fun setup() {
+        val instrumentation = InstrumentationRegistry.getInstrumentation()
+        // This sample font provides the following features:
+        // 1. The width of most of visible characters equals to font size.
+        // 2. The LTR/RTL characters are rendered as ▶/◀.
+        // 3. The fontMetrics passed to TextPaint has descend - ascend equal to 1.2 * fontSize.
+        // 4. The fontMetrics passed to TextPaint has ascend equal to fontSize.
+        sampleTypeface = ResourcesCompat.getFont(instrumentation.context, R.font.sample_font)!!
+    }
+
+    @Test
+    fun excludesLineBreak_whenMaxLinesPresent_withoutEllipsis() {
+        val text = "abc\ndef"
+        val textSize = 20.0f
+
+        val layout = simpleLayout(
+            text = text,
+            textSize = textSize,
+            layoutWidth = textSize * 10,
+            maxLines = 1
+        )
+
+        assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+    }
+
+    @Test
+    fun excludesLineBreak_whenMaxLinesPresent_withEllipsisEnd() {
+        val text = "abc\ndef"
+        val textSize = 20.0f
+
+        val layout = simpleLayout(
+            text = text,
+            textSize = textSize,
+            layoutWidth = textSize * 10,
+            maxLines = 1,
+            ellipsize = TextUtils.TruncateAt.END
+        )
+
+        assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+    }
+
+    @Test
+    fun excludesWhitespace_singleLineContent_withEllipsis() {
+        val text = "abc def ghi"
+        val textSize = 20.0f
+        val layoutWidth = textSize * 10
+
+        val layout = simpleLayout(
+            text = text,
+            textSize = textSize,
+            layoutWidth = layoutWidth,
+            ellipsize = TextUtils.TruncateAt.END
+        )
+
+        assertThat(layout.getLineVisibleEnd(0)).isEqualTo(7)
+    }
+
+    @Test
+    fun excludesWhitespace_multiLineContent_withoutEllipsis() {
+        val text = "abc def    \nghi"
+        val textSize = 20.0f
+        val layoutWidth = textSize * 10
+
+        val layout = simpleLayout(
+            text = text,
+            textSize = textSize,
+            layoutWidth = layoutWidth
+        )
+
+        // the way overflown text layout is calculated with ellipsis is vastly different before and
+        // after API 23. Line visible end logic cannot be unified below API 23.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(7)
+        } else {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+        }
+    }
+
+    @Test
+    fun excludesWhitespace_multiLineContent_withEllipsis() {
+        val text = "abc def    \nghi"
+        val textSize = 20.0f
+        val layoutWidth = textSize * 10
+
+        val layout = simpleLayout(
+            text = text,
+            textSize = textSize,
+            layoutWidth = layoutWidth,
+            ellipsize = TextUtils.TruncateAt.END
+        )
+
+        // the way overflown text layout is calculated with ellipsis is vastly different before and
+        // after API 23. Line visible end logic cannot be unified below API 23.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(7)
+        } else {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+        }
+    }
+
+    @Test
+    fun noVisibleContent_multiLine_withoutEllipsis() {
+        val text = "\n\nabc"
+        val textSize = 20.0f
+        val layoutWidth = textSize * 10
+
+        val layout = simpleLayout(
+            text = text,
+            textSize = textSize,
+            layoutWidth = layoutWidth,
+            maxLines = 2
+        )
+
+        assertThat(layout.getLineVisibleEnd(0)).isEqualTo(0)
+        assertThat(layout.getLineVisibleEnd(1)).isEqualTo(1)
+    }
+
+    @Test
+    fun noVisibleContent_multiLine_withEllipsis() {
+        val text = "\n\nabc"
+        val textSize = 20.0f
+        val layoutWidth = textSize * 10
+
+        val layout = simpleLayout(
+            text = text,
+            textSize = textSize,
+            layoutWidth = layoutWidth,
+            maxLines = 2,
+            ellipsize = TextUtils.TruncateAt.END
+        )
+
+        assertThat(layout.getLineVisibleEnd(0)).isEqualTo(0)
+        assertThat(layout.getLineVisibleEnd(1)).isEqualTo(2) // ellipsis character
+    }
+
+    private fun simpleLayout(
+        text: CharSequence,
+        textSize: Float = Float.NaN,
+        layoutWidth: Float = textSize * text.length,
+        ellipsize: TextUtils.TruncateAt? = null,
+        maxLines: Int = Int.MAX_VALUE
+    ): TextLayout {
+        val textPaint = TextPaint()
+        textPaint.typeface = sampleTypeface
+        textPaint.textSize = if (!textSize.isNaN()) textSize else 14f
+
+        return TextLayout(
+            charSequence = text,
+            width = layoutWidth,
+            textPaint = textPaint,
+            maxLines = maxLines,
+            ellipsize = ellipsize
+        )
+    }
+}
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt
index 62e7978..4deeab9 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutHelper.kt
@@ -247,7 +247,7 @@
 
         // Use line visible end for creating bidi object since invisible whitespaces should not be
         // considered for location retrieval.
-        val lineVisibleEnd = lineEndToVisibleEnd(lineEnd)
+        val lineVisibleEnd = lineEndToVisibleEnd(lineEnd, lineStart)
         val paragraphStart = getParagraphStart(paraNo)
         val bidiStart = lineStart - paragraphStart
         val bidiEnd = lineVisibleEnd - paragraphStart
@@ -314,7 +314,7 @@
             // out of bounds for the runs in this Bidi. We are adjusting the requested offset
             // to the visible end of line.
             val lineEndAdjustedOffset = if (offset > lineVisibleEnd) {
-                lineEndToVisibleEnd(offset)
+                lineEndToVisibleEnd(offset, lineStart)
             } else {
                 offset
             }
@@ -349,6 +349,14 @@
         }
     }
 
+    /**
+     * Return the text offset after the last visible character on the specified line. For example
+     * whitespaces are not counted as visible characters.
+     */
+    fun getLineVisibleEnd(lineIndex: Int): Int {
+        return lineEndToVisibleEnd(layout.getLineEnd(lineIndex), layout.getLineStart(lineIndex))
+    }
+
     private fun getDownstreamHorizontal(offset: Int, primary: Boolean): Float {
         val lineNo = layout.getLineForOffset(offset)
         val lineEnd = layout.getLineEnd(lineNo)
@@ -370,10 +378,13 @@
 
     private data class BidiRun(val start: Int, val end: Int, val isRtl: Boolean)
 
-    // Convert line end offset to the offset that is the last visible character.
-    private fun lineEndToVisibleEnd(lineEnd: Int): Int {
+    /**
+     * Convert line end offset to the offset that is the last visible character. Last visible
+     * character on this line cannot be before line start.
+     */
+    private fun lineEndToVisibleEnd(lineEnd: Int, lineStart: Int): Int {
         var visibleEnd = lineEnd
-        while (visibleEnd > 0) {
+        while (visibleEnd > lineStart) {
             if (isLineEndSpace(layout.text[visibleEnd - 1 /* visibleEnd is exclusive */])) {
                 visibleEnd--
             } else {
@@ -385,6 +396,7 @@
 
     // The spaces that will not be rendered if they are placed at the line end. In most case, it is
     // whitespace or line feed character, hence checking linearly should be enough.
+    @Suppress("ConvertTwoComparisonsToRangeCheck")
     fun isLineEndSpace(c: Char) = c == ' ' || c == '\n' || c == '\u1680' ||
-        (c in '\u2000'..'\u200A' && c != '\u2007') || c == '\u205F' || c == '\u3000'
+        (c >= '\u2000' && c <= '\u200A' && c != '\u2007') || c == '\u205F' || c == '\u3000'
 }
\ No newline at end of file
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
index 3944f11..1f88bd0 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
@@ -462,7 +462,7 @@
      */
     fun getLineVisibleEnd(lineIndex: Int): Int =
         if (layout.getEllipsisStart(lineIndex) == 0) { // no ellipsis
-            layout.getLineVisibleEnd(lineIndex)
+            layoutHelper.getLineVisibleEnd(lineIndex)
         } else {
             layout.getLineStart(lineIndex) + layout.getEllipsisStart(lineIndex)
         }
diff --git a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
index 855ac02..369392a 100644
--- a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
+++ b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
@@ -16,6 +16,8 @@
 
 package androidx.tv.integration.playground
 
+import android.content.Context
+import android.view.accessibility.AccessibilityManager
 import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
@@ -25,6 +27,7 @@
 import androidx.compose.animation.togetherWith
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -45,9 +48,15 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.semantics.CollectionItemInfo
+import androidx.compose.ui.semantics.collectionItemInfo
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
@@ -134,7 +143,7 @@
             modifier = Modifier
                 .background(backgrounds[itemIndex])
                 .fillMaxSize()
-                .semantics { contentDescription = "Featured Content" }
+                .carouselItemSemantics(itemIndex, contentDescription = "Featured Content")
         ) {
             Column(
                 modifier = Modifier
@@ -155,6 +164,41 @@
     }
 }
 
+@OptIn(ExperimentalComposeUiApi::class)
+@Suppress("ComposableModifierFactory")
+@Composable
+internal fun Modifier.carouselItemSemantics(
+    itemIndex: Int,
+    contentDescription: String?
+): Modifier {
+    val focusManager = LocalFocusManager.current
+    val context = LocalContext.current
+    val accessibilityManager = remember {
+        context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+    }
+
+    return this
+        .semantics(mergeDescendants = true) {
+            contentDescription?.let {
+                this.contentDescription = it
+            }
+            collectionItemInfo =
+                CollectionItemInfo(
+                    rowIndex = 0,
+                    rowSpan = 1,
+                    columnIndex = itemIndex,
+                    columnSpan = 1
+                )
+        }
+        .then(
+            if (accessibilityManager.isEnabled) {
+                Modifier.clickable {
+                    focusManager.moveFocus(FocusDirection.Enter)
+                }
+            } else Modifier
+        )
+}
+
 @Composable
 private fun OverlayButton(modifier: Modifier = Modifier, text: String = "Play") {
     var isFocused by remember { mutableStateOf(false) }
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/SwitchTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/SwitchTest.kt
index fbe4bb2..8d2efaa 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/SwitchTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/SwitchTest.kt
@@ -221,17 +221,17 @@
         }
 
         rule.onNodeWithTag("spacer", useUnmergedTree = true)
-            .assertLeftPositionInRootIsEqualTo(8.dp)
+            .assertLeftPositionInRootIsEqualTo(4.dp)
 
         rule.runOnIdle { checked = true }
 
         rule.onNodeWithTag("spacer", useUnmergedTree = true)
-            .assertLeftPositionInRootIsEqualTo(28.dp)
+            .assertLeftPositionInRootIsEqualTo(20.dp)
 
         rule.runOnIdle { checked = false }
 
         rule.onNodeWithTag("spacer", useUnmergedTree = true)
-            .assertLeftPositionInRootIsEqualTo(8.dp)
+            .assertLeftPositionInRootIsEqualTo(4.dp)
     }
 
     @Test
@@ -258,7 +258,7 @@
             }
 
         rule.onNodeWithTag("spacer", useUnmergedTree = true)
-            .assertLeftPositionInRootIsEqualTo(8.dp)
+            .assertLeftPositionInRootIsEqualTo(4.dp)
     }
 
     // regression test for b/191375128
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
index 5fbfc80c..dc403a6 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
@@ -16,6 +16,8 @@
 
 package androidx.tv.material3
 
+import android.content.Context
+import android.view.accessibility.AccessibilityManager
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedContentScope
 import androidx.compose.animation.AnimatedVisibilityScope
@@ -62,10 +64,14 @@
 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.LocalContext
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.semantics.CollectionInfo
+import androidx.compose.ui.semantics.ScrollAxisRange
 import androidx.compose.ui.semantics.collectionInfo
+import androidx.compose.ui.semantics.horizontalScrollAxisRange
+import androidx.compose.ui.semantics.scrollBy
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
@@ -123,18 +129,20 @@
     val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
     val carouselOuterBoxFocusRequester = remember { FocusRequester() }
     var isAutoScrollActive by remember { mutableStateOf(false) }
+    val context = LocalContext.current
+    val accessibilityManager = remember {
+        context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
+    }
 
     AutoScrollSideEffect(
         autoScrollDurationMillis = autoScrollDurationMillis,
         itemCount = itemCount,
         carouselState = carouselState,
-        doAutoScroll = shouldPerformAutoScroll(focusState),
+        doAutoScroll = shouldPerformAutoScroll(focusState, accessibilityManager),
         onAutoScrollChange = { isAutoScrollActive = it })
 
     Box(modifier = modifier
-        .semantics {
-            collectionInfo = CollectionInfo(rowCount = 1, columnCount = itemCount)
-        }
+        .carouselSemantics(itemCount = itemCount, state = carouselState)
         .bringIntoViewIfChildrenAreFocused()
         .focusRequester(carouselOuterBoxFocusRequester)
         .onFocusChanged {
@@ -165,6 +173,9 @@
             label = "CarouselAnimation"
         ) { activeItemIndex ->
             LaunchedEffect(Unit) {
+                if (accessibilityManager.isEnabled) {
+                    carouselOuterBoxFocusRequester.requestFocus()
+                }
                 [email protected] {
                     // Outer box is focused
                     if (!isAutoScrollActive && focusState?.isFocused == true) {
@@ -186,10 +197,15 @@
 }
 
 @Composable
-private fun shouldPerformAutoScroll(focusState: FocusState?): Boolean {
+private fun shouldPerformAutoScroll(
+    focusState: FocusState?,
+    accessibilityManager: AccessibilityManager
+): Boolean {
     val carouselIsFocused = focusState?.isFocused ?: false
     val carouselHasFocus = focusState?.hasFocus ?: false
-    return !(carouselIsFocused || carouselHasFocus)
+
+    // Disable auto scroll when accessibility mode is enabled or the carousel is focused
+    return !accessibilityManager.isEnabled && !(carouselIsFocused || carouselHasFocus)
 }
 
 @Suppress("IllegalExperimentalApiUsage")
@@ -487,3 +503,52 @@
         }
     }
 }
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Suppress("ComposableModifierFactory")
+@Composable
+internal fun Modifier.carouselSemantics(
+    itemCount: Int,
+    state: CarouselState
+): Modifier {
+    return this.then(
+        remember(
+            state,
+            itemCount
+        ) {
+            val accessibilityScrollState = ScrollAxisRange(
+                value = {
+                    // Active slide index represents the current position
+                    state.activeItemIndex.toFloat()
+                },
+                maxValue = {
+                    // Last slide index represents the max. value
+                    (itemCount - 1).toFloat()
+                },
+                reverseScrolling = false
+            )
+
+            val scrollByAction: ((x: Float, y: Float) -> Boolean) =
+                { x, _ ->
+                    when {
+                        // Positive value of x represents forward scrolling
+                        x > 0f -> state.moveToNextItem(itemCount)
+
+                        // Negative value of x represents backward scrolling
+                        x < 0f -> state.moveToPreviousItem(itemCount)
+                    }
+
+                    // Return false for non-horizontal scrolling (x==0)
+                    x != 0f
+                }
+
+            Modifier.semantics {
+                horizontalScrollAxisRange = accessibilityScrollState
+
+                scrollBy(action = scrollByAction)
+
+                collectionInfo = CollectionInfo(rowCount = 1, columnCount = itemCount)
+            }
+        }
+    )
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Switch.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Switch.kt
index d71168a..61d51b2 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Switch.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Switch.kt
@@ -24,7 +24,6 @@
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.collectIsPressedAsState
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.height
@@ -178,27 +177,13 @@
     maxBound: Dp,
 ) {
     val trackColor by colors.trackColor(enabled, checked)
-    val isPressed by interactionSource.collectIsPressedAsState()
 
     val thumbValueDp = with(LocalDensity.current) { thumbValue.value.toDp() }
-    val thumbSizeDp = if (isPressed) {
-        SwitchTokens.PressedHandleWidth
-    } else {
-        uncheckedThumbDiameter + (ThumbDiameter - uncheckedThumbDiameter) *
-            ((thumbValueDp - minBound) / (maxBound - minBound))
-    }
 
-    val thumbOffset = if (isPressed) {
-        with(LocalDensity.current) {
-            if (checked) {
-                ThumbPathLength - SwitchTokens.TrackOutlineWidth
-            } else {
-                SwitchTokens.TrackOutlineWidth
-            }.toPx()
-        }
-    } else {
-        thumbValue.value
-    }
+    val thumbSizeDp = uncheckedThumbDiameter + (ThumbDiameter - uncheckedThumbDiameter) *
+        ((thumbValueDp - minBound) / (maxBound - minBound))
+
+    val thumbOffset = thumbValue.value
 
     val trackShape = SwitchTokens.TrackShape.toShape()
     val modifier = Modifier
@@ -328,7 +313,7 @@
     /**
      * Icon size to use for `thumbContent`
      */
-    val IconSize = 16.dp
+    val IconSize = 12.dp
 }
 
 /**
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/SwitchTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/SwitchTokens.kt
index 498364f..bb29efd 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/SwitchTokens.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/SwitchTokens.kt
@@ -34,48 +34,48 @@
     val DisabledUnselectedTrackColor = ColorSchemeKeyTokens.SurfaceVariant
     val DisabledUnselectedTrackOutlineColor = ColorSchemeKeyTokens.OnSurface
     val HandleShape = ShapeKeyTokens.CornerFull
-    val PressedHandleHeight = 28.0.dp
-    val PressedHandleWidth = 28.0.dp
+    val PressedHandleHeight = 18.0.dp
+    val PressedHandleWidth = 18.0.dp
     val SelectedFocusHandleColor = ColorSchemeKeyTokens.PrimaryContainer
     val SelectedFocusIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
     val SelectedFocusTrackColor = ColorSchemeKeyTokens.Primary
     val SelectedHandleColor = ColorSchemeKeyTokens.OnPrimary
-    val SelectedHandleHeight = 24.0.dp
-    val SelectedHandleWidth = 24.0.dp
+    val SelectedHandleHeight = 18.0.dp
+    val SelectedHandleWidth = 18.0.dp
     val SelectedHoverHandleColor = ColorSchemeKeyTokens.PrimaryContainer
     val SelectedHoverIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
     val SelectedHoverTrackColor = ColorSchemeKeyTokens.Primary
     val SelectedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
-    val SelectedIconSize = 16.0.dp
+    val SelectedIconSize = 12.0.dp
     val SelectedPressedHandleColor = ColorSchemeKeyTokens.PrimaryContainer
     val SelectedPressedIconColor = ColorSchemeKeyTokens.OnPrimaryContainer
     val SelectedPressedTrackColor = ColorSchemeKeyTokens.Primary
     val SelectedTrackColor = ColorSchemeKeyTokens.Primary
     val StateLayerShape = ShapeKeyTokens.CornerFull
     val StateLayerSize = 40.0.dp
-    val TrackHeight = 32.0.dp
-    val TrackOutlineWidth = 2.0.dp
+    val TrackHeight = 24.0.dp
+    val TrackOutlineWidth = 1.5.dp
     val TrackShape = ShapeKeyTokens.CornerFull
-    val TrackWidth = 52.0.dp
+    val TrackWidth = 40.0.dp
     val UnselectedFocusHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val UnselectedFocusIconColor = ColorSchemeKeyTokens.SurfaceVariant
     val UnselectedFocusTrackColor = ColorSchemeKeyTokens.SurfaceVariant
     val UnselectedFocusTrackOutlineColor = ColorSchemeKeyTokens.Border
     val UnselectedHandleColor = ColorSchemeKeyTokens.Border
-    val UnselectedHandleHeight = 16.0.dp
-    val UnselectedHandleWidth = 16.0.dp
+    val UnselectedHandleHeight = 12.0.dp
+    val UnselectedHandleWidth = 12.0.dp
     val UnselectedHoverHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val UnselectedHoverIconColor = ColorSchemeKeyTokens.SurfaceVariant
     val UnselectedHoverTrackColor = ColorSchemeKeyTokens.SurfaceVariant
     val UnselectedHoverTrackOutlineColor = ColorSchemeKeyTokens.Border
     val UnselectedIconColor = ColorSchemeKeyTokens.SurfaceVariant
-    val UnselectedIconSize = 16.0.dp
+    val UnselectedIconSize = 12.0.dp
     val UnselectedPressedHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val UnselectedPressedIconColor = ColorSchemeKeyTokens.SurfaceVariant
     val UnselectedPressedTrackColor = ColorSchemeKeyTokens.SurfaceVariant
     val UnselectedPressedTrackOutlineColor = ColorSchemeKeyTokens.Border
     val UnselectedTrackColor = ColorSchemeKeyTokens.SurfaceVariant
     val UnselectedTrackOutlineColor = ColorSchemeKeyTokens.Border
-    val IconHandleHeight = 24.0.dp
-    val IconHandleWidth = 24.0.dp
+    val IconHandleHeight = 18.0.dp
+    val IconHandleWidth = 18.0.dp
 }
\ No newline at end of file