Merge changes from topic "revert-3454934-tc_change_2501170544_1-PDFZXELQSF" into androidx-main

* changes:
  Revert "Import translations. DO NOT MERGE ANYWHERE"
  Revert "Import translations. DO NOT MERGE ANYWHERE"
diff --git a/appfunctions/appfunctions-common/src/main/java/androidx/appfunctions/AppFunctionSchemaDefinition.kt b/appfunctions/appfunctions-common/src/main/java/androidx/appfunctions/AppFunctionSchemaDefinition.kt
new file mode 100644
index 0000000..3469992
--- /dev/null
+++ b/appfunctions/appfunctions-common/src/main/java/androidx/appfunctions/AppFunctionSchemaDefinition.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appfunctions
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.RestrictTo.Scope
+
+/**
+ * Annotates an interface defining the schema for an app function, outlining its input, output, and
+ * behavior
+ *
+ * Example Usage:
+ * ```kotlin
+ * @AppFunctionSchemaDefinition(name = "findNotes", version = 1, category = "Notes")
+ * interface FindNotes {
+ *   suspend fun findNotes(
+ *     appFunctionContext: AppFunctionContext,
+ *     findNotesParams: FindNotesParams,
+ *   ): List<Note>
+ * }
+ * ```
+ */
+@RestrictTo(Scope.LIBRARY_GROUP)
+@Retention(
+    // Binary because it's used to determine the annotation values from the compiled schema library.
+    AnnotationRetention.BINARY
+)
+@Target(AnnotationTarget.CLASS)
+public annotation class AppFunctionSchemaDefinition(
+    val name: String,
+    val version: Int,
+    val category: String
+)
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
index 6151d45..8acc8bc 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
@@ -20,6 +20,7 @@
 import androidx.appfunctions.compiler.core.logException
 import androidx.appfunctions.compiler.processors.AppFunctionIdProcessor
 import androidx.appfunctions.compiler.processors.AppFunctionInventoryProcessor
+import androidx.appfunctions.compiler.processors.AppFunctionLegacyIndexXmlProcessor
 import com.google.devtools.ksp.processing.KSPLogger
 import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.processing.SymbolProcessor
@@ -53,8 +54,11 @@
         override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
             val idProcessor = AppFunctionIdProcessor(environment.codeGenerator)
             val inventoryProcessor = AppFunctionInventoryProcessor(environment.codeGenerator)
+            // TODO: Add compiler option to disable legacy xml generator.
+            val legacyIndexXmlProcessor =
+                AppFunctionLegacyIndexXmlProcessor(environment.codeGenerator)
             return AppFunctionCompiler(
-                listOf(idProcessor, inventoryProcessor),
+                listOf(idProcessor, inventoryProcessor, legacyIndexXmlProcessor),
                 environment.logger,
             )
         }
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/AppFunctionSymbolResolver.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/AppFunctionSymbolResolver.kt
index 2bac5ac..a7d1d5c 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/AppFunctionSymbolResolver.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/AppFunctionSymbolResolver.kt
@@ -20,6 +20,7 @@
 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionAnnotation
 import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSFile
 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
 
 /** The helper class to resolve AppFunction related symbols. */
@@ -113,5 +114,8 @@
             val methodName = functionDeclaration.simpleName.asString()
             return "${packageName}.${className}#${methodName}"
         }
+
+        /** Returns the file containing the class declaration and app functions. */
+        fun getSourceFile(): KSFile? = classDeclaration.containingFile
     }
 }
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
index fe0d3fa..6f90f16 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
@@ -28,6 +28,14 @@
     // Annotation classes
     object AppFunctionAnnotation {
         val CLASS_NAME = ClassName(APP_FUNCTIONS_PACKAGE_NAME, "AppFunction")
+        const val PROPERTY_IS_ENABLED = "isEnabled"
+    }
+
+    object AppFunctionSchemaDefinitionAnnotation {
+        val CLASS_NAME = ClassName(APP_FUNCTIONS_PACKAGE_NAME, "AppFunctionSchemaDefinition")
+        const val PROPERTY_CATEGORY = "category"
+        const val PROPERTY_NAME = "name"
+        const val PROPERTY_VERSION = "version"
     }
 
     // Classes
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/KspUtils.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/KspUtils.kt
index 1fa4707..f723a5cb 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/KspUtils.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/KspUtils.kt
@@ -16,8 +16,12 @@
 
 package androidx.appfunctions.compiler.core
 
+import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSName
 import com.google.devtools.ksp.symbol.KSTypeReference
 import com.squareup.kotlinpoet.ClassName
+import kotlin.reflect.KClass
+import kotlin.reflect.cast
 
 /**
  * Checks if the type reference is of the given type.
@@ -27,12 +31,45 @@
  * @throws ProcessingException If unable to resolve the type.
  */
 fun KSTypeReference.isOfType(type: ClassName): Boolean {
-    val ksType = this.resolve()
     val typeName =
-        ksType.declaration.qualifiedName
+        resolveTypeName()
             ?: throw ProcessingException(
                 "Unable to resolve the type to check if it is of type [${type}]",
                 this
             )
     return typeName.asString() == type.canonicalName
 }
+
+/**
+ * Finds and returns an annotation of [annotationClass] type.
+ *
+ * @param annotationClass the annotation class to find
+ */
+fun Sequence<KSAnnotation>.findAnnotation(annotationClass: ClassName): KSAnnotation? =
+    this.singleOrNull() {
+        val shortName = it.shortName.getShortName()
+        if (shortName != annotationClass.simpleName) {
+            false
+        } else {
+            val typeName =
+                it.annotationType.resolveTypeName()
+                    ?: throw ProcessingException(
+                        "Unable to resolve type for [$shortName]",
+                        it.annotationType
+                    )
+            typeName.asString() == annotationClass.canonicalName
+        }
+    }
+
+private fun KSTypeReference.resolveTypeName(): KSName? = resolve().declaration.qualifiedName
+
+/** Returns the value of the annotation property if found. */
+fun <T : Any> KSAnnotation.requirePropertyValueOfType(
+    propertyName: String,
+    expectedType: KClass<T>,
+): T {
+    val propertyValue =
+        this.arguments.singleOrNull { it.name?.asString() == propertyName }?.value
+            ?: throw ProcessingException("Unable to find property with name: $propertyName", this)
+    return expectedType.cast(propertyValue)
+}
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionLegacyIndexXmlProcessor.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionLegacyIndexXmlProcessor.kt
new file mode 100644
index 0000000..d41a4d3e
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionLegacyIndexXmlProcessor.kt
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appfunctions.compiler.processors
+
+import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver
+import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver.AnnotatedAppFunctions
+import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionAnnotation
+import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSchemaDefinitionAnnotation
+import androidx.appfunctions.compiler.core.ProcessingException
+import androidx.appfunctions.compiler.core.findAnnotation
+import androidx.appfunctions.compiler.core.requirePropertyValueOfType
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSFunctionDeclaration
+import com.squareup.kotlinpoet.ClassName
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.transform.OutputKeys
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.dom.DOMSource
+import javax.xml.transform.stream.StreamResult
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+
+/**
+ * Generates AppFunction's index xml file for the legacy AppSearch indexer to index.
+ *
+ * The generator would write an XML file as `/assets/app_functions.xml`. The file would be packaged
+ * into the APK's asset when assembled. So that the AppSearch indexer can look up the asset and
+ * inject metadata into platform AppSearch database accordingly.
+ *
+ * The new indexer will index additional properties based on the schema defined in SDK instead of
+ * the pre-defined one in AppSearch.
+ */
+class AppFunctionLegacyIndexXmlProcessor(
+    private val codeGenerator: CodeGenerator,
+) : SymbolProcessor {
+
+    override fun process(resolver: Resolver): List<KSAnnotated> {
+        generateLegacyIndexXml(AppFunctionSymbolResolver(resolver).resolveAnnotatedAppFunctions())
+        return emptyList()
+    }
+
+    /**
+     * Generates AppFunction's legacy index xml files for v1 indexer in App Search.
+     *
+     * @param appFunctionsByClass a collection of functions annotated with @AppFunction grouped by
+     *   their enclosing classes.
+     */
+    private fun generateLegacyIndexXml(
+        appFunctionsByClass: List<AnnotatedAppFunctions>,
+    ) {
+        if (appFunctionsByClass.isEmpty()) {
+            return
+        }
+        val xmlDetails = appFunctionsByClass.flatMap(::getAppFunctionXmlDetail)
+        writeXmlFile(xmlDetails, appFunctionsByClass)
+    }
+
+    private fun getAppFunctionXmlDetail(
+        appFunctionsByClass: AnnotatedAppFunctions
+    ): List<AppFunctionXmlDetails> {
+
+        return appFunctionsByClass.appFunctionDeclarations.map {
+            val appFunctionAnnotation =
+                it.annotations.findAnnotation(AppFunctionAnnotation.CLASS_NAME)
+                    ?: throw ProcessingException("Function not annotated with @AppFunction.", it)
+            val enabled =
+                appFunctionAnnotation.requirePropertyValueOfType(
+                    AppFunctionAnnotation.PROPERTY_IS_ENABLED,
+                    Boolean::class,
+                )
+
+            val schemaDetail = getAppFunctionSchemaDetail(it)
+
+            AppFunctionXmlDetails(
+                appFunctionsByClass.getAppFunctionIdentifier(it),
+                enabled,
+                schemaDetail,
+            )
+        }
+    }
+
+    private fun getAppFunctionSchemaDetail(
+        function: KSFunctionDeclaration
+    ): AppFunctionSchemaDetail? {
+        val rootInterfaceWithAppFunctionSchemaDefinition =
+            findRootInterfaceWithAnnotation(
+                function,
+                AppFunctionSchemaDefinitionAnnotation.CLASS_NAME
+            ) ?: return null
+
+        val schemaFunctionAnnotation =
+            rootInterfaceWithAppFunctionSchemaDefinition.annotations.findAnnotation(
+                AppFunctionSchemaDefinitionAnnotation.CLASS_NAME
+            ) ?: return null
+        val schemaCategory =
+            schemaFunctionAnnotation.requirePropertyValueOfType(
+                AppFunctionSchemaDefinitionAnnotation.PROPERTY_CATEGORY,
+                String::class,
+            )
+        val schemaName =
+            schemaFunctionAnnotation.requirePropertyValueOfType(
+                AppFunctionSchemaDefinitionAnnotation.PROPERTY_NAME,
+                String::class,
+            )
+        val schemaVersion =
+            schemaFunctionAnnotation.requirePropertyValueOfType(
+                AppFunctionSchemaDefinitionAnnotation.PROPERTY_VERSION,
+                Int::class,
+            )
+        return AppFunctionSchemaDetail(schemaCategory, schemaName, schemaVersion)
+    }
+
+    private fun findRootInterfaceWithAnnotation(
+        function: KSFunctionDeclaration,
+        annotationName: ClassName
+    ): KSClassDeclaration? {
+        val parentDeclaration = function.parentDeclaration as? KSClassDeclaration ?: return null
+
+        // Check if the enclosing class has the @AppFunctionSchemaDefinition
+        val annotation = parentDeclaration.annotations.findAnnotation(annotationName)
+        if (annotation != null) {
+            return parentDeclaration
+        }
+
+        val superClassFunction = (function.findOverridee() as? KSFunctionDeclaration) ?: return null
+        return findRootInterfaceWithAnnotation(superClassFunction, annotationName)
+    }
+
+    private fun writeXmlFile(
+        xmlDetailsList: List<AppFunctionXmlDetails>,
+        appFunctionsByClass: List<AnnotatedAppFunctions>,
+    ) {
+        val xmlDocumentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
+        val xmlDocument = xmlDocumentBuilder.newDocument().apply { xmlStandalone = true }
+
+        val appFunctionsElement = xmlDocument.createElement(XmlElement.APP_FUNCTIONS_ELEMENTS_TAG)
+        xmlDocument.appendChild(appFunctionsElement)
+
+        for (xmlDetails in xmlDetailsList) {
+            appFunctionsElement.appendChild(xmlDocument.createAppFunctionElement(xmlDetails))
+        }
+
+        val transformer =
+            TransformerFactory.newInstance().newTransformer().apply {
+                setOutputProperty(OutputKeys.INDENT, "yes")
+                setOutputProperty(OutputKeys.ENCODING, "UTF-8")
+                setOutputProperty(OutputKeys.VERSION, "1.0")
+                setOutputProperty(OutputKeys.STANDALONE, "yes")
+            }
+
+        codeGenerator
+            .createNewFile(
+                Dependencies(
+                    aggregating = true,
+                    *appFunctionsByClass.mapNotNull { it.getSourceFile() }.toTypedArray()
+                ),
+                XML_PACKAGE_NAME,
+                XML_FILE_NAME,
+                XML_EXTENSION
+            )
+            .use { stream -> transformer.transform(DOMSource(xmlDocument), StreamResult(stream)) }
+    }
+
+    private fun Document.createAppFunctionElement(xmlDetails: AppFunctionXmlDetails): Element =
+        createElement(XmlElement.APP_FUNCTION_ITEM_TAG).apply {
+            appendChild(
+                createElementWithTextNode(XmlElement.APP_FUNCTION_ID_TAG, xmlDetails.functionId)
+            )
+
+            val schemaDetail = xmlDetails.schemaDetail
+            if (schemaDetail != null) {
+                appendChild(
+                    createElementWithTextNode(
+                        XmlElement.APP_FUNCTION_SCHEMA_CATEGORY_TAG,
+                        schemaDetail.schemaCategory,
+                    )
+                )
+                appendChild(
+                    createElementWithTextNode(
+                        XmlElement.APP_FUNCTION_SCHEMA_NAME_TAG,
+                        schemaDetail.schemaName,
+                    )
+                )
+                appendChild(
+                    createElementWithTextNode(
+                        XmlElement.APP_FUNCTION_SCHEMA_VERSION_TAG,
+                        schemaDetail.schemaVersion.toString(),
+                    )
+                )
+            }
+            appendChild(
+                createElementWithTextNode(
+                    XmlElement.APP_FUNCTION_ENABLE_BY_DEFAULT_TAG,
+                    xmlDetails.enabled.toString(),
+                )
+            )
+        }
+
+    private fun Document.createElementWithTextNode(elementName: String, text: String): Element =
+        createElement(elementName).apply { appendChild(createTextNode(text)) }
+
+    /** Details of an app function that are needed to generate its XML file. */
+    private data class AppFunctionXmlDetails(
+        val functionId: String,
+        val enabled: Boolean,
+        val schemaDetail: AppFunctionSchemaDetail?,
+    )
+
+    /** Details of an schema function that are needed to generate its XML file. */
+    private data class AppFunctionSchemaDetail(
+        val schemaCategory: String,
+        val schemaName: String,
+        val schemaVersion: Int,
+    )
+
+    private companion object {
+        private const val XML_PACKAGE_NAME = "assets"
+        private const val XML_FILE_NAME = "app_functions"
+        private const val XML_EXTENSION = "xml"
+
+        private object XmlElement {
+            const val APP_FUNCTIONS_ELEMENTS_TAG = "appfunctions"
+            const val APP_FUNCTION_ITEM_TAG = "appfunction"
+            const val APP_FUNCTION_ID_TAG = "function_id"
+            const val APP_FUNCTION_SCHEMA_CATEGORY_TAG = "schema_category"
+            const val APP_FUNCTION_SCHEMA_NAME_TAG = "schema_name"
+            const val APP_FUNCTION_SCHEMA_VERSION_TAG = "schema_version"
+            const val APP_FUNCTION_ENABLE_BY_DEFAULT_TAG = "enabled_by_default"
+        }
+    }
+}
diff --git a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
index 8860052..39b1ebb 100644
--- a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
+++ b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
@@ -46,9 +46,9 @@
     fun testSimpleFunction_genAppFunctionIds_success() {
         val report = compilationTestHelper.compileAll(sourceFileNames = listOf("SimpleFunction.KT"))
 
-        compilationTestHelper.assertSuccessWithContent(
+        compilationTestHelper.assertSuccessWithSourceContent(
             report = report,
-            expectGeneratedFileName = "SimpleFunctionIds.kt",
+            expectGeneratedSourceFileName = "SimpleFunctionIds.kt",
             goldenFileName = "SimpleFunctionIds.KT"
         )
     }
@@ -85,10 +85,25 @@
     fun testSimpleFunction_genAppFunctionInventoryImpl_success() {
         val report = compilationTestHelper.compileAll(sourceFileNames = listOf("SimpleFunction.KT"))
 
-        compilationTestHelper.assertSuccessWithContent(
+        compilationTestHelper.assertSuccessWithSourceContent(
             report = report,
-            expectGeneratedFileName = "SimpleFunction_AppFunctionInventory_Impl.kt",
+            expectGeneratedSourceFileName = "SimpleFunction_AppFunctionInventory_Impl.kt",
             goldenFileName = "$%s".format("SimpleFunction_AppFunctionInventory_Impl.KT")
         )
     }
+
+    // TODO: Add more tests for legacy index processor.
+    @Test
+    fun testSampleNoParamImp_genLegacyIndexXmlFile_success() {
+        val report =
+            compilationTestHelper.compileAll(
+                sourceFileNames = listOf("FakeNoArgImpl.KT", "FakeSchemas.KT")
+            )
+
+        compilationTestHelper.assertSuccessWithResourceContent(
+            report = report,
+            expectGeneratedResourceFileName = "app_functions.xml",
+            goldenFileName = "fakeNoArgImpl_app_function.xml"
+        )
+    }
 }
diff --git a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt
index 4e6253d..cbb3c4b 100644
--- a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt
+++ b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/testings/CompilationTestHelper.kt
@@ -17,6 +17,7 @@
 package androidx.appfunctions.compiler.testings
 
 import androidx.room.compiler.processing.util.DiagnosticMessage
+import androidx.room.compiler.processing.util.Resource
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
 import androidx.room.compiler.processing.util.compiler.TestCompilationResult
@@ -99,28 +100,20 @@
     }
 
     /**
-     * Asserts that the compilation succeeds and contains [expectGeneratedFileName] in generated
-     * sources that is identical to the content of [goldenFileName].
+     * Asserts that the compilation succeeds and contains [expectGeneratedSourceFileName] in
+     * generated sources that is identical to the content of [goldenFileName].
      */
-    fun assertSuccessWithContent(
+    fun assertSuccessWithSourceContent(
         report: CompilationReport,
-        expectGeneratedFileName: String,
+        expectGeneratedSourceFileName: String,
         goldenFileName: String,
     ) {
-        Truth.assertWithMessage(
-                """
-                Compile failed with error:
-                ${report.printDiagnostics(Diagnostic.Kind.ERROR)}
-            """
-                    .trimIndent()
-            )
-            .that(report.isSuccess)
-            .isTrue()
+        assertCompilationSuccess(report)
 
         val goldenFile = getGoldenFile(goldenFileName)
         val generatedSourceFile =
             report.generatedSourceFiles.single { sourceFile ->
-                sourceFile.source.relativePath.contains(expectGeneratedFileName)
+                sourceFile.source.relativePath.contains(expectGeneratedSourceFileName)
             }
         Truth.assertWithMessage(
                 """
@@ -136,6 +129,48 @@
             .isEqualTo(goldenFile.readText())
     }
 
+    /**
+     * Asserts that the compilation succeeds and contains [expectGeneratedResourceFileName] in
+     * generated resources that is identical to the content of [goldenFileName].
+     */
+    fun assertSuccessWithResourceContent(
+        report: CompilationReport,
+        expectGeneratedResourceFileName: String,
+        goldenFileName: String,
+    ) {
+        assertCompilationSuccess(report)
+
+        val goldenFile = getGoldenFile(goldenFileName)
+        val generatedResourceFile =
+            report.generatedResourceFiles.single { resourceFile ->
+                resourceFile.resource.relativePath.contains(expectGeneratedResourceFileName)
+            }
+        Truth.assertWithMessage(
+                """
+              Content of generated file [${generatedResourceFile.resource.relativePath}] does not match
+              the content of golden file [${goldenFile.path}].
+
+              To update the golden file,
+              run `cp ${generatedResourceFile.resourceFilePath} ${goldenFile.absolutePath}`
+            """
+                    .trimIndent()
+            )
+            .that(generatedResourceFile.resource.getContents())
+            .isEqualTo(goldenFile.readText())
+    }
+
+    private fun assertCompilationSuccess(report: CompilationReport) {
+        Truth.assertWithMessage(
+                """
+                    Compile failed with error:
+                    ${report.printDiagnostics(Diagnostic.Kind.ERROR)}
+                """
+                    .trimIndent()
+            )
+            .that(report.isSuccess)
+            .isTrue()
+    }
+
     fun assertErrorWithMessage(report: CompilationReport, expectedErrorMessage: String) {
         Truth.assertWithMessage("Compile succeed").that(report.isSuccess).isFalse()
 
@@ -196,6 +231,8 @@
         val generatedSourceFiles: List<GeneratedSourceFile>,
         /** A map of diagnostics results. */
         val diagnostics: Map<Diagnostic.Kind, List<DiagnosticMessage>>,
+        /** A list of generated source files. */
+        val generatedResourceFiles: List<GeneratedResourceFile>,
     ) {
         /** Print the diagnostics result of type [kind]. */
         fun printDiagnostics(kind: Diagnostic.Kind): String {
@@ -216,7 +253,11 @@
                         result.generatedSources.map { source ->
                             GeneratedSourceFile.create(source, outputDir)
                         },
-                    diagnostics = result.diagnostics
+                    diagnostics = result.diagnostics,
+                    generatedResourceFiles =
+                        result.generatedResources.map { resource ->
+                            GeneratedResourceFile.create(resource, outputDir)
+                        }
                 )
             }
         }
@@ -236,4 +277,22 @@
             }
         }
     }
+
+    /** A wrapper class contains [Resource] with its file path. */
+    data class GeneratedResourceFile(val resource: Resource, val resourceFilePath: Path) {
+        companion object {
+            internal fun create(resource: Resource, outputDir: Path): GeneratedResourceFile {
+                val filePath =
+                    outputDir.resolve(resource.relativePath).apply {
+                        parent?.createDirectories()
+                        createFile()
+                        writeText(resource.getContents())
+                    }
+                return GeneratedResourceFile(resource, filePath)
+            }
+        }
+    }
 }
+
+private fun Resource.getContents(): String =
+    openInputStream().bufferedReader().use { it.readText() }
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/input/FakeNoArgImpl.KT b/appfunctions/appfunctions-compiler/src/test/test-data/input/FakeNoArgImpl.KT
new file mode 100644
index 0000000..4b09194
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/input/FakeNoArgImpl.KT
@@ -0,0 +1,8 @@
+package com.testdata
+
+import androidx.appfunctions.AppFunction
+import androidx.appfunctions.AppFunctionContext
+
+class FakeNoArgImpl : FakeNoArg {
+    @AppFunction override fun noArg(appFunctionContext: AppFunctionContext) {}
+}
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/input/FakeSchemas.KT b/appfunctions/appfunctions-compiler/src/test/test-data/input/FakeSchemas.KT
new file mode 100644
index 0000000..a59c737
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/input/FakeSchemas.KT
@@ -0,0 +1,11 @@
+package com.testdata
+
+import androidx.appfunctions.AppFunctionContext
+import androidx.appfunctions.AppFunctionSchemaDefinition
+
+private const val FAKE_CATEGORY = "fake_schema_category"
+
+@AppFunctionSchemaDefinition(name = "noArg", version = 1, category = FAKE_CATEGORY)
+interface FakeNoArg {
+    fun noArg(appFunctionContext: AppFunctionContext)
+}
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_app_function.xml b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_app_function.xml
new file mode 100644
index 0000000..229bae7
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/output/fakeNoArgImpl_app_function.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<appfunctions>
+    <appfunction>
+        <function_id>com.testdata.FakeNoArgImpl#noArg</function_id>
+        <schema_category>fake_schema_category</schema_category>
+        <schema_name>noArg</schema_name>
+        <schema_version>1</schema_version>
+        <enabled_by_default>true</enabled_by_default>
+    </appfunction>
+</appfunctions>
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt
index 2f84338..0c80536 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryProvider.kt
@@ -96,9 +96,6 @@
         Debug.traceStart { "Create CameraPipe" }
         val timeSource = SystemTimeSource()
         val start = Timestamps.now(timeSource)
-        // Enable pruning device manager when tested in the MH lab.
-        val usePruningDeviceManager =
-            android.util.Log.isLoggable(CAMERA_PIPE_MH_FLAG, android.util.Log.DEBUG)
 
         val cameraPipe =
             CameraPipe(
@@ -110,16 +107,11 @@
                             sharedInteropCallbacks.sessionStateCallback,
                             openRetryMaxTimeout
                         ),
-                    usePruningDeviceManager = usePruningDeviceManager
+                    usePruningDeviceManager = true
                 )
             )
         Log.debug { "Created CameraPipe in ${start.measureNow(timeSource).formatMs()}" }
         Debug.traceStop()
         return cameraPipe
     }
-
-    private companion object {
-        // Flag set when being tested in the lab. Refer to CameraPipeConfigTestRule for more info.
-        const val CAMERA_PIPE_MH_FLAG = "CameraPipeMH"
-    }
 }
diff --git a/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/PdfViewZoomStateTest.kt b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/PdfViewZoomStateTest.kt
new file mode 100644
index 0000000..c04af8b
--- /dev/null
+++ b/pdf/pdf-viewer/src/androidTest/kotlin/androidx/pdf/view/PdfViewZoomStateTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.pdf.view
+
+import android.graphics.Point
+import android.graphics.PointF
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.core.app.ActivityScenario
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class PdfViewZoomStateTest {
+
+    private lateinit var activityScenario: ActivityScenario<PdfViewTestActivity>
+
+    @Before
+    fun setup() {
+        activityScenario = ActivityScenario.launch(PdfViewTestActivity::class.java)
+    }
+
+    @After
+    fun tearDown() {
+        PdfViewTestActivity.onCreateCallback = {}
+        activityScenario.close()
+    }
+
+    private fun setupPdfView(fakePdfDocument: FakePdfDocument?) {
+        PdfViewTestActivity.onCreateCallback = { activity ->
+            val container = FrameLayout(activity)
+            val pdfView =
+                PdfView(activity).apply {
+                    pdfDocument = fakePdfDocument
+                    id = PDF_VIEW_ID
+                    minZoom = 0.5f
+                    maxZoom = 5.0f
+                }
+            container.addView(pdfView, ViewGroup.LayoutParams(PAGE_WIDTH, PAGE_HEIGHT * 2))
+            activity.setContentView(container)
+        }
+    }
+
+    @Test
+    fun testInitialZoom_fitWidth() = runTest {
+        val fakePdfDocument = FakePdfDocument(List(20) { Point(PAGE_WIDTH, PAGE_HEIGHT) })
+
+        setupPdfView(fakePdfDocument)
+
+        with(ActivityScenario.launch(PdfViewTestActivity::class.java)) {
+            fakePdfDocument.waitForLayout(untilPage = 3)
+            onView(withId(PDF_VIEW_ID)).check { view, noViewFoundException ->
+                view ?: throw noViewFoundException
+                val pdfView = view as PdfView
+                assertThat(pdfView.isInitialZoomDone).isTrue()
+                assertThat(pdfView.zoom).isWithin(0.01f).of(1.0f)
+            }
+        }
+    }
+
+    @Test
+    fun testGetDefaultZoom_fitWidth() = runTest {
+        val fakePdfDocument = FakePdfDocument(List(20) { Point(PAGE_WIDTH, PAGE_HEIGHT) })
+
+        setupPdfView(fakePdfDocument)
+        activityScenario.recreate()
+
+        activityScenario.onActivity { activity ->
+            val pdfView = activity.findViewById<PdfView>(PDF_VIEW_ID)
+            pdfView.zoom = 2.0f
+            val expectedZoom = 1.0f
+            val actualZoom = pdfView.getDefaultZoom()
+            assertThat(actualZoom).isWithin(0.01f).of(expectedZoom)
+        }
+    }
+
+    @Test
+    fun testRestoreUserZoomAndScrollPosition() = runTest {
+        val fakePdfDocument = FakePdfDocument(List(20) { Point(PAGE_WIDTH, PAGE_HEIGHT) })
+        val savedZoom = 2.5f
+        val savedScrollPosition = PointF(100f, PAGE_HEIGHT * 1f / savedZoom)
+
+        setupPdfView(fakePdfDocument)
+        activityScenario.recreate()
+
+        activityScenario.onActivity { activity ->
+            val pdfView = activity.findViewById<PdfView>(PDF_VIEW_ID)
+            pdfView.zoom = savedZoom
+            pdfView.scrollTo(
+                (savedScrollPosition.x * savedZoom - pdfView.viewportWidth / 2f).toInt(),
+                (savedScrollPosition.y * savedZoom - pdfView.viewportHeight / 2f).toInt()
+            )
+            pdfView.isInitialZoomDone = true
+        }
+
+        activityScenario.recreate()
+
+        onView(withId(PDF_VIEW_ID)).check { view, _ ->
+            view as PdfView
+            assertThat(view.zoom).isWithin(0.01f).of(savedZoom)
+            val expectedScrollX =
+                (savedScrollPosition.x * savedZoom - view.viewportWidth / 2f).toInt()
+            val expectedScrollY =
+                (savedScrollPosition.y * savedZoom - view.viewportHeight / 2f).toInt()
+            assertThat(view.scrollX).isEqualTo(expectedScrollX)
+            assertThat(view.scrollY).isEqualTo(expectedScrollY)
+        }
+    }
+}
+
+/** Arbitrary fixed ID for PdfView */
+private const val PDF_VIEW_ID = 123456789
+private const val PAGE_WIDTH = 500
+private const val PAGE_HEIGHT = 800
+
+/** The height of the viewport, minus padding */
+val PdfView.viewportHeight: Int
+    get() = bottom - top - paddingBottom - paddingTop
+
+/** The width of the viewport, minus padding */
+val PdfView.viewportWidth: Int
+    get() = right - left - paddingRight - paddingLeft
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfView.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfView.kt
index 0c1973b..2621d74 100644
--- a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfView.kt
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfView.kt
@@ -215,6 +215,7 @@
     private var awaitingFirstLayout: Boolean = true
     private var scrollPositionToRestore: PointF? = null
     private var zoomToRestore: Float? = null
+    @VisibleForTesting internal var isInitialZoomDone: Boolean = false
     /**
      * The width of the PdfView before the last layout change (e.g., before rotation). Used to
      * preserve the zoom level when the device is rotated.
@@ -521,6 +522,7 @@
         val superState = super.onSaveInstanceState()
         val state = PdfViewSavedState(superState)
         state.zoom = zoom
+        state.isInitialZoomDone = isInitialZoomDone
         state.viewWidth = width
         state.contentCenterX = toContentX(viewportWidth.toFloat() / 2f)
         state.contentCenterY = toContentY(viewportHeight.toFloat() / 2f)
@@ -605,7 +607,8 @@
         }
     }
 
-    private fun getDefaultZoom(): Float {
+    @VisibleForTesting
+    internal fun getDefaultZoom(): Float {
         if (contentWidth == 0 || viewportWidth == 0) return DEFAULT_INIT_ZOOM
         val widthZoom = viewportWidth.toFloat() / contentWidth
         return MathUtils.clamp(widthZoom, minZoom, maxZoom)
@@ -649,6 +652,7 @@
             scrollPositionToRestore = positionToRestore
             zoomToRestore = localStateToRestore.zoom
             oldWidth = localStateToRestore.viewWidth
+            isInitialZoomDone = localStateToRestore.isInitialZoomDone
         } else {
             scrollToRestoredPosition(positionToRestore, localStateToRestore.zoom)
         }
@@ -927,7 +931,10 @@
         // centering if it's needed. It doesn't override any restored state because we're scrolling
         // to the current scroll position.
         if (pageNum == 0) {
-            this.zoom = getDefaultZoom()
+            if (!isInitialZoomDone) {
+                this.zoom = getDefaultZoom()
+                isInitialZoomDone = true
+            }
             scrollTo(scrollX, scrollY)
         }
 
diff --git a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfViewSavedState.kt b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfViewSavedState.kt
index c0c5047..dd0ab91 100644
--- a/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfViewSavedState.kt
+++ b/pdf/pdf-viewer/src/main/kotlin/androidx/pdf/view/PdfViewSavedState.kt
@@ -30,6 +30,7 @@
     var zoom: Float = 1F
     var documentUri: Uri? = null
     var paginationModel: PaginationModel? = null
+    var isInitialZoomDone: Boolean = false
     /**
      * The width of the PdfView before the last layout change (e.g., before rotation). Used to
      * preserve the zoom level when the device is rotated.
diff --git a/room/room-common/build.gradle b/room/room-common/build.gradle
index 1cf5db4..e8eed4f 100644
--- a/room/room-common/build.gradle
+++ b/room/room-common/build.gradle
@@ -32,12 +32,14 @@
 }
 
 androidXMultiplatform {
+    js()
     jvm() {
         withJava()
     }
     mac()
     linux()
     ios()
+    wasmJs()
 
     defaultPlatform(PlatformIdentifier.JVM)
 
@@ -45,7 +47,7 @@
         commonMain {
             dependencies {
                 api(libs.kotlinStdlib)
-                api("androidx.annotation:annotation:1.8.1")
+                api("androidx.annotation:annotation:1.9.1")
             }
         }
 
diff --git a/room/room-common/src/jsMain/kotlin/androidx/room/ConstructedBy.js.kt b/room/room-common/src/jsMain/kotlin/androidx/room/ConstructedBy.js.kt
new file mode 100644
index 0000000..b43bd1e
--- /dev/null
+++ b/room/room-common/src/jsMain/kotlin/androidx/room/ConstructedBy.js.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+
+import kotlin.reflect.AssociatedObjectKey
+import kotlin.reflect.ExperimentalAssociatedObjects
+import kotlin.reflect.KClass
+
+/**
+ * Defines the [androidx.room.RoomDatabaseConstructor] that will instantiate the Room generated
+ * implementation of the annotated [Database].
+ *
+ * A [androidx.room.RoomDatabase] database definition must be annotated with this annotation if it
+ * is located in a common source set on a Kotlin Multiplatform project such that at runtime the
+ * implementation generated by the annotation processor can be used. The [value] must be an 'expect
+ * object' that implements [androidx.room.RoomDatabaseConstructor].
+ *
+ * Example usage:
+ * ```
+ * @Database(version = 1, entities = [Song::class, Album::class])
+ * @ConstructedBy(MusicDatabaseConstructor::class)
+ * abstract class MusicDatabase : RoomDatabase
+ *
+ * expect object MusicDatabaseConstructor : RoomDatabaseConstructor<MusicDatabase>
+ * ```
+ *
+ * @see androidx.room.RoomDatabaseConstructor
+ */
+@OptIn(ExperimentalAssociatedObjects::class)
+@AssociatedObjectKey
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.BINARY)
+actual annotation class ConstructedBy(
+    /**
+     * The 'expect' declaration of an 'object' that implements
+     * [androidx.room.RoomDatabaseConstructor] and is able to instantiate a
+     * [androidx.room.RoomDatabase].
+     */
+    actual val value: KClass<*>
+)
diff --git a/room/room-common/src/wasmJsMain/kotlin/androidx/room/ConstructedBy.wasmJs.kt b/room/room-common/src/wasmJsMain/kotlin/androidx/room/ConstructedBy.wasmJs.kt
new file mode 100644
index 0000000..b43bd1e
--- /dev/null
+++ b/room/room-common/src/wasmJsMain/kotlin/androidx/room/ConstructedBy.wasmJs.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+
+import kotlin.reflect.AssociatedObjectKey
+import kotlin.reflect.ExperimentalAssociatedObjects
+import kotlin.reflect.KClass
+
+/**
+ * Defines the [androidx.room.RoomDatabaseConstructor] that will instantiate the Room generated
+ * implementation of the annotated [Database].
+ *
+ * A [androidx.room.RoomDatabase] database definition must be annotated with this annotation if it
+ * is located in a common source set on a Kotlin Multiplatform project such that at runtime the
+ * implementation generated by the annotation processor can be used. The [value] must be an 'expect
+ * object' that implements [androidx.room.RoomDatabaseConstructor].
+ *
+ * Example usage:
+ * ```
+ * @Database(version = 1, entities = [Song::class, Album::class])
+ * @ConstructedBy(MusicDatabaseConstructor::class)
+ * abstract class MusicDatabase : RoomDatabase
+ *
+ * expect object MusicDatabaseConstructor : RoomDatabaseConstructor<MusicDatabase>
+ * ```
+ *
+ * @see androidx.room.RoomDatabaseConstructor
+ */
+@OptIn(ExperimentalAssociatedObjects::class)
+@AssociatedObjectKey
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.BINARY)
+actual annotation class ConstructedBy(
+    /**
+     * The 'expect' declaration of an 'object' that implements
+     * [androidx.room.RoomDatabaseConstructor] and is able to instantiate a
+     * [androidx.room.RoomDatabase].
+     */
+    actual val value: KClass<*>
+)
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 1fb47dc..ca1d072 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -1650,12 +1650,10 @@
   }
 
   public final class TimeTextDefaults {
-    method public float getAutoTextWeight();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimeSource rememberTimeSource(String timeFormat);
     method @androidx.compose.runtime.Composable public String timeFormat();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.text.TextStyle timeTextStyle(optional long background, optional long color, optional long fontSize);
-    property public final float AutoTextWeight;
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.foundation.CurvedTextStyle timeTextStyle(optional long background, optional long color, optional long fontSize);
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     property public static final float MaxSweepAngle;
     property public static final String TimeFormat12Hours;
@@ -1667,14 +1665,9 @@
   }
 
   public final class TimeTextKt {
-    method @androidx.compose.runtime.Composable public static void TimeText(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.CurvedModifier curvedModifier, optional float maxSweepAngle, optional androidx.wear.compose.material3.TimeSource timeSource, optional androidx.compose.ui.text.TextStyle timeTextStyle, optional long contentColor, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.TimeTextScope,kotlin.Unit> content);
-  }
-
-  public abstract sealed class TimeTextScope {
-    method public abstract void composable(kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method public abstract void separator(optional androidx.compose.ui.text.TextStyle? style);
-    method public abstract void text(String text, optional androidx.compose.ui.text.TextStyle? style, optional float weight);
-    method public abstract void time();
+    method @androidx.compose.runtime.Composable public static void TimeText(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.CurvedModifier curvedModifier, optional float maxSweepAngle, optional androidx.wear.compose.material3.TimeSource timeSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.CurvedScope,? super java.lang.String,kotlin.Unit> content);
+    method public static void timeTextCurvedText(androidx.wear.compose.foundation.CurvedScope, String time, optional androidx.wear.compose.foundation.CurvedTextStyle? style);
+    method public static void timeTextSeparator(androidx.wear.compose.foundation.CurvedScope, optional androidx.wear.compose.foundation.CurvedTextStyle? curvedTextStyle, optional androidx.wear.compose.foundation.ArcPaddingValues contentArcPadding);
   }
 
   public final class TouchTargetAwareSizeKt {
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 1fb47dc..ca1d072 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -1650,12 +1650,10 @@
   }
 
   public final class TimeTextDefaults {
-    method public float getAutoTextWeight();
     method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimeSource rememberTimeSource(String timeFormat);
     method @androidx.compose.runtime.Composable public String timeFormat();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.text.TextStyle timeTextStyle(optional long background, optional long color, optional long fontSize);
-    property public final float AutoTextWeight;
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.foundation.CurvedTextStyle timeTextStyle(optional long background, optional long color, optional long fontSize);
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     property public static final float MaxSweepAngle;
     property public static final String TimeFormat12Hours;
@@ -1667,14 +1665,9 @@
   }
 
   public final class TimeTextKt {
-    method @androidx.compose.runtime.Composable public static void TimeText(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.CurvedModifier curvedModifier, optional float maxSweepAngle, optional androidx.wear.compose.material3.TimeSource timeSource, optional androidx.compose.ui.text.TextStyle timeTextStyle, optional long contentColor, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.material3.TimeTextScope,kotlin.Unit> content);
-  }
-
-  public abstract sealed class TimeTextScope {
-    method public abstract void composable(kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method public abstract void separator(optional androidx.compose.ui.text.TextStyle? style);
-    method public abstract void text(String text, optional androidx.compose.ui.text.TextStyle? style, optional float weight);
-    method public abstract void time();
+    method @androidx.compose.runtime.Composable public static void TimeText(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.CurvedModifier curvedModifier, optional float maxSweepAngle, optional androidx.wear.compose.material3.TimeSource timeSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.CurvedScope,? super java.lang.String,kotlin.Unit> content);
+    method public static void timeTextCurvedText(androidx.wear.compose.foundation.CurvedScope, String time, optional androidx.wear.compose.foundation.CurvedTextStyle? style);
+    method public static void timeTextSeparator(androidx.wear.compose.foundation.CurvedScope, optional androidx.wear.compose.foundation.CurvedTextStyle? curvedTextStyle, optional androidx.wear.compose.foundation.ArcPaddingValues contentArcPadding);
   }
 
   public final class TouchTargetAwareSizeKt {
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollAwayDemos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollAwayDemos.kt
index ce9aa66..9b3d8c3 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollAwayDemos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ScrollAwayDemos.kt
@@ -41,6 +41,7 @@
 import androidx.wear.compose.material3.ScreenStage
 import androidx.wear.compose.material3.Text
 import androidx.wear.compose.material3.TimeText
+import androidx.wear.compose.material3.curvedText
 import androidx.wear.compose.material3.samples.ScrollAwaySample
 import androidx.wear.compose.material3.scrollAway
 
@@ -100,7 +101,7 @@
                         else ScreenStage.Idle
                     }
                 ),
-            content = { text("ScrollAway") }
+            content = { curvedText("ScrollAway") }
         )
     }
 }
@@ -151,7 +152,7 @@
                         else ScreenStage.Idle
                     }
                 ),
-            content = { text("ScrollAway") }
+            content = { curvedText("ScrollAway") }
         )
     }
 }
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt
index 397f24d..d53c350 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimeTextDemo.kt
@@ -33,6 +33,7 @@
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import androidx.wear.compose.foundation.curvedComposable
 import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.integration.demos.common.ComposableDemo
 import androidx.wear.compose.material3.Button
@@ -43,13 +44,17 @@
 import androidx.wear.compose.material3.Text
 import androidx.wear.compose.material3.TimeText
 import androidx.wear.compose.material3.TimeTextDefaults
+import androidx.wear.compose.material3.curvedText
 import androidx.wear.compose.material3.samples.TimeTextClockOnly
 import androidx.wear.compose.material3.samples.TimeTextWithStatus
+import androidx.wear.compose.material3.samples.TimeTextWithStatusEllipsized
+import androidx.wear.compose.material3.timeTextSeparator
 
 val TimeTextDemos =
     listOf(
         ComposableDemo("Clock only") { TimeTextClockOnly() },
         ComposableDemo("Clock with Status") { TimeTextWithStatus() },
+        ComposableDemo("Clock with Ellipsized Status") { TimeTextWithStatusEllipsized() },
         ComposableDemo("Clock with long Status") { TimeTextWithLongStatus() },
         ComposableDemo("Clock with Icon") { TimeTextWithIcon() },
         ComposableDemo("Clock with custom colors") { TimeTextWithCustomColors() },
@@ -61,10 +66,10 @@
 
 @Composable
 fun TimeTextWithLongStatus() {
-    TimeText {
-        text("Some long leading text")
-        separator()
-        time()
+    TimeText { time ->
+        curvedText("Some long leading text")
+        timeTextSeparator()
+        curvedText(time)
     }
 }
 
@@ -72,12 +77,12 @@
 fun TimeTextWithCustomColors() {
     val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Red)
 
-    TimeText {
-        text("ETA", customStyle)
-        composable { Spacer(modifier = Modifier.size(4.dp)) }
-        text("12:48")
-        separator()
-        time()
+    TimeText { time ->
+        curvedText("ETA", style = customStyle)
+        curvedComposable { Spacer(modifier = Modifier.size(4.dp)) }
+        curvedText("12:48")
+        timeTextSeparator()
+        curvedText(time)
     }
 }
 
@@ -85,19 +90,19 @@
 fun TimeTextCustomSize() {
     val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Green, fontSize = 24.sp)
 
-    TimeText {
-        text("ETA", customStyle)
-        separator()
-        time()
+    TimeText { time ->
+        curvedText("ETA", style = customStyle)
+        timeTextSeparator()
+        curvedText(time)
     }
 }
 
 @Composable
 fun TimeTextWithIcon() {
-    TimeText {
-        time()
-        separator()
-        composable {
+    TimeText { time ->
+        curvedText(time)
+        timeTextSeparator()
+        curvedComposable {
             Icon(
                 imageVector = Icons.Filled.Favorite,
                 contentDescription = "Favorite",
@@ -151,13 +156,13 @@
             items(10) { Text("Some extra items ($it) to scroll", Modifier.padding(5.dp)) }
         }
         // Timetext later so it's on top.
-        TimeText { time() }
+        TimeText { time -> curvedText(time) }
     }
 }
 
 @Composable
 fun TimeTextOnScreenWhiteBackground() {
-    Box(Modifier.fillMaxSize().background(Color.White)) { TimeText { time() } }
+    Box(Modifier.fillMaxSize().background(Color.White)) { TimeText { time -> curvedText(time) } }
 }
 
 @Composable
@@ -166,7 +171,7 @@
         MaterialTheme(
             colorScheme = MaterialTheme.colorScheme.copy(background = Color.Transparent)
         ) {
-            TimeText { time() }
+            TimeText { time -> curvedText(time) }
         }
     }
 }
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollAwaySample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollAwaySample.kt
index 225dd56f..42f1ba7 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollAwaySample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/ScrollAwaySample.kt
@@ -33,7 +33,9 @@
 import androidx.wear.compose.material3.ScreenStage
 import androidx.wear.compose.material3.Text
 import androidx.wear.compose.material3.TimeText
+import androidx.wear.compose.material3.curvedText
 import androidx.wear.compose.material3.scrollAway
+import androidx.wear.compose.material3.timeTextSeparator
 
 @Sampled
 @Composable
@@ -73,10 +75,10 @@
                         if (state.isScrollInProgress) ScreenStage.Scrolling else ScreenStage.Idle
                     }
                 ),
-            content = {
-                text("ScrollAway")
-                separator()
-                time()
+            content = { time ->
+                curvedText("ScrollAway")
+                timeTextSeparator()
+                curvedText(time)
             }
         )
     }
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimeTextSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimeTextSample.kt
index 020e098..47ba1e6 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimeTextSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimeTextSample.kt
@@ -18,9 +18,14 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.wear.compose.foundation.CurvedModifier
+import androidx.wear.compose.foundation.weight
 import androidx.wear.compose.material3.MaterialTheme
 import androidx.wear.compose.material3.TimeText
 import androidx.wear.compose.material3.TimeTextDefaults
+import androidx.wear.compose.material3.curvedText
+import androidx.wear.compose.material3.timeTextSeparator
 
 @Sampled
 @Composable
@@ -34,9 +39,23 @@
 fun TimeTextWithStatus() {
     val primaryStyle =
         TimeTextDefaults.timeTextStyle(color = MaterialTheme.colorScheme.primaryContainer)
-    TimeText {
-        text("ETA 12:48", style = primaryStyle)
-        separator()
-        time()
+    TimeText { time ->
+        curvedText("ETA 12:48", style = primaryStyle)
+        timeTextSeparator()
+        curvedText(time)
+    }
+}
+
+@Sampled
+@Composable
+fun TimeTextWithStatusEllipsized() {
+    TimeText { time ->
+        curvedText(
+            "Long status that should be ellipsized.",
+            CurvedModifier.weight(1f),
+            overflow = TextOverflow.Ellipsis
+        )
+        timeTextSeparator()
+        curvedText(time)
     }
 }
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt
index 90674cb..4849bdc 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ScrollAwayTest.kt
@@ -48,6 +48,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.wear.compose.foundation.ScrollInfoProvider
+import androidx.wear.compose.foundation.curvedComposable
 import androidx.wear.compose.foundation.lazy.AutoCenteringParams
 import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.foundation.lazy.ScalingLazyListState
@@ -207,7 +208,7 @@
                             )
                             .testTag(TIME_TEXT_TAG),
                 ) {
-                    composable { Box(Modifier.size(20.dp).background(timeTextColor)) }
+                    curvedComposable { Box(Modifier.size(20.dp).background(timeTextColor)) }
                 }
             }
         }
@@ -233,7 +234,7 @@
                             )
                             .testTag(TIME_TEXT_TAG)
                 ) {
-                    composable { Box(Modifier.size(20.dp).background(timeTextColor)) }
+                    curvedComposable { Box(Modifier.size(20.dp).background(timeTextColor)) }
                 }
                 LazyColumn(state = scrollState, modifier = Modifier.testTag(SCROLL_TAG)) {
                     item { ListHeader { Text("Buttons") } }
@@ -253,7 +254,6 @@
                         .testTag(TEST_TAG)
             ) {
                 TimeText(
-                    contentColor = timeTextColor,
                     modifier =
                         Modifier.scrollAway(
                                 scrollInfoProvider = ScrollInfoProvider(scrollState),
@@ -264,7 +264,7 @@
                             )
                             .testTag(TIME_TEXT_TAG)
                 ) {
-                    composable { Box(Modifier.size(20.dp).background(timeTextColor)) }
+                    curvedComposable { Box(Modifier.size(20.dp).background(timeTextColor)) }
                 }
                 Column(modifier = Modifier.verticalScroll(scrollState).testTag(SCROLL_TAG)) {
                     ListHeader { Text("Buttons") }
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt
index 5c81306..526b932 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextScreenshotTest.kt
@@ -29,23 +29,25 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.DeviceConfigurationOverride
 import androidx.compose.ui.test.ForcedSize
-import androidx.compose.ui.test.RoundScreen
 import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.then
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.screenshot.AndroidXScreenshotTestRule
-import com.google.testing.junit.testparameterinjector.TestParameter
-import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import androidx.wear.compose.foundation.CurvedModifier
+import androidx.wear.compose.foundation.CurvedScope
+import androidx.wear.compose.foundation.curvedComposable
+import androidx.wear.compose.foundation.weight
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TestName
 import org.junit.runner.RunWith
 
 @MediumTest
-@RunWith(TestParameterInjector::class)
+@RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
 class TimeTextScreenshotTest {
     @get:Rule val rule = createComposeRule()
@@ -60,60 +62,34 @@
         }
 
     @Test
-    fun time_text_with_clock_only_on_round_device() = verifyScreenshot {
+    fun time_text_with_clock_only() = verifyScreenshot {
         TimeText(
             modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
             timeSource = MockTimeSource,
-        ) {
-            time()
+        )
+    }
+
+    @Test
+    fun time_text_with_status() = verifyScreenshot {
+        TimeText(
+            modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
+            timeSource = MockTimeSource,
+        ) { time ->
+            curvedText("ETA 12:48")
+            timeTextSeparator()
+            curvedText(time)
         }
     }
 
     @Test
-    fun time_text_with_clock_only_on_non_round_device() =
-        verifyScreenshot(false) {
-            TimeText(
-                modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
-                timeSource = MockTimeSource,
-            ) {
-                time()
-            }
-        }
-
-    @Test
-    fun time_text_with_status_on_round_device() = verifyScreenshot {
+    fun time_text_with_icon() = verifyScreenshot {
         TimeText(
             modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
             timeSource = MockTimeSource,
-        ) {
-            text("ETA 12:48")
-            separator()
-            time()
-        }
-    }
-
-    @Test
-    fun time_text_with_status_on_non_round_device() =
-        verifyScreenshot(false) {
-            TimeText(
-                modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
-                timeSource = MockTimeSource,
-            ) {
-                text("ETA 12:48")
-                separator()
-                time()
-            }
-        }
-
-    @Test
-    fun time_text_with_icon_on_round_device() = verifyScreenshot {
-        TimeText(
-            modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
-            timeSource = MockTimeSource,
-        ) {
-            time()
-            separator()
-            composable {
+        ) { time ->
+            curvedText(time)
+            timeTextSeparator()
+            curvedComposable {
                 Icon(
                     imageVector = Icons.Filled.Favorite,
                     contentDescription = "Favorite",
@@ -124,193 +100,126 @@
     }
 
     @Test
-    fun time_text_with_icon_on_non_round_device() =
-        verifyScreenshot(false) {
-            TimeText(
-                modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
-                timeSource = MockTimeSource,
-            ) {
-                time()
-                separator()
-                composable {
-                    Icon(
-                        imageVector = Icons.Filled.Favorite,
-                        contentDescription = "Favorite",
-                        modifier = Modifier.size(13.dp)
-                    )
-                }
-            }
-        }
-
-    @Test
-    fun time_text_with_custom_colors_on_round_device() = verifyScreenshot {
+    fun time_text_with_custom_colors() = verifyScreenshot {
         val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Red)
         val timeTextStyle = TimeTextDefaults.timeTextStyle(color = Color.Cyan)
         val separatorStyle = TimeTextDefaults.timeTextStyle(color = Color.Yellow)
         TimeText(
-            contentColor = Color.Green,
-            timeTextStyle = timeTextStyle,
             modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
             timeSource = MockTimeSource,
-        ) {
-            text("ETA", customStyle)
-            composable { Spacer(modifier = Modifier.size(4.dp)) }
-            text("12:48")
-            separator(separatorStyle)
-            time()
+        ) { time ->
+            curvedText("ETA 12:48", style = customStyle)
+            curvedComposable { Spacer(modifier = Modifier.size(4.dp)) }
+            curvedText("12:48", style = customStyle)
+            timeTextSeparator(separatorStyle)
+            curvedText(time, style = timeTextStyle)
         }
     }
 
     @Test
-    fun time_text_with_long_status_on_round_device() = verifyScreenshot {
+    fun time_text_with_long_status() = verifyScreenshot {
         val timeTextStyle = TimeTextDefaults.timeTextStyle(color = Color.Green)
         TimeText(
-            contentColor = Color.Green,
-            timeTextStyle = timeTextStyle,
             modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
             timeSource = MockTimeSource,
-        ) {
-            text("Long status that should be ellipsized.")
-            composable { Spacer(modifier = Modifier.size(4.dp)) }
-            time()
-        }
-    }
-
-    @Test
-    fun time_text_with_custom_colors_on_non_round_device() =
-        verifyScreenshot(false) {
-            val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Red)
-            val timeTextStyle = TimeTextDefaults.timeTextStyle(color = Color.Cyan)
-            val separatorStyle = TimeTextDefaults.timeTextStyle(color = Color.Yellow)
-            TimeText(
-                contentColor = Color.Green,
-                timeTextStyle = timeTextStyle,
-                modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
-                timeSource = MockTimeSource,
-            ) {
-                text("ETA", customStyle)
-                composable { Spacer(modifier = Modifier.size(4.dp)) }
-                text("12:48")
-                separator(separatorStyle)
-                time()
-            }
-        }
-
-    @Test
-    fun time_text_with_very_long_text_on_round_device() =
-        verifyScreenshot(true) {
-            val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Red)
-            val timeTextStyle = TimeTextDefaults.timeTextStyle(color = Color.Cyan)
-            val separatorStyle = TimeTextDefaults.timeTextStyle(color = Color.Yellow)
-            TimeText(
-                contentColor = Color.Green,
-                timeTextStyle = timeTextStyle,
-                maxSweepAngle = 180f,
-                modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
-                timeSource = MockTimeSource,
-            ) {
-                text(
-                    "Very long text to ensure we are respecting the maxSweep parameter",
-                    customStyle
-                )
-                separator(separatorStyle)
-                time()
-            }
-        }
-
-    @Test
-    fun time_text_with_very_long_text_non_round_device() =
-        verifyScreenshot(false) {
-            val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Red)
-            val timeTextStyle = TimeTextDefaults.timeTextStyle(color = Color.Cyan)
-            val separatorStyle = TimeTextDefaults.timeTextStyle(color = Color.Yellow)
-            TimeText(
-                contentColor = Color.Green,
-                timeTextStyle = timeTextStyle,
-                modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
-                timeSource = MockTimeSource,
-            ) {
-                text(
-                    "Very long text to ensure we are not taking more than one line and " +
-                        "leaving room for the time",
-                    customStyle
-                )
-                separator(separatorStyle)
-                time()
-            }
-        }
-
-    @Test
-    fun time_text_with_very_long_text_smaller_angle_on_round_device() =
-        verifyScreenshot(true) {
-            val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Red)
-            val timeTextStyle = TimeTextDefaults.timeTextStyle(color = Color.Cyan)
-            val separatorStyle = TimeTextDefaults.timeTextStyle(color = Color.Yellow)
-            TimeText(
-                contentColor = Color.Green,
-                timeTextStyle = timeTextStyle,
-                maxSweepAngle = 90f,
-                modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
-                timeSource = MockTimeSource,
-            ) {
-                text(
-                    "Very long text to ensure we are respecting the maxSweep parameter",
-                    customStyle
-                )
-                separator(separatorStyle)
-                time()
-            }
-        }
-
-    @Test
-    fun time_text_long_text_before_time(@TestParameter shape: ScreenShape) =
-        TimeTextWithDefaults(shape.isRound) {
-            text("Very long text to ensure we are respecting the weight parameter", weight = 1f)
-            separator()
-            time()
-            separator()
-            text("More")
-        }
-
-    @Test
-    fun time_text_long_text_after_time(@TestParameter shape: ScreenShape) =
-        TimeTextWithDefaults(shape.isRound) {
-            text("More")
-            separator()
-            time()
-            separator()
-            text("Very long text to ensure we are respecting the weight parameter", weight = 1f)
-        }
-
-    // This is to get better names, so it says 'round_device' instead of 'true'
-    enum class ScreenShape(val isRound: Boolean) {
-        ROUND_DEVICE(true),
-        SQUARE_DEVICE(false)
-    }
-
-    private fun TimeTextWithDefaults(isDeviceRound: Boolean, content: TimeTextScope.() -> Unit) =
-        verifyScreenshot(isDeviceRound) {
-            TimeText(
-                contentColor = Color.Green,
-                maxSweepAngle = 180f,
-                modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
-                timeSource = MockTimeSource,
-                content = content
+        ) { time ->
+            curvedText(
+                "Long status that should be ellipsized.",
+                CurvedModifier.weight(1f),
+                overflow = TextOverflow.Ellipsis,
+                style = timeTextStyle
             )
+            curvedComposable { Spacer(modifier = Modifier.size(4.dp)) }
+            curvedText(time, style = timeTextStyle)
         }
+    }
 
-    private fun verifyScreenshot(isDeviceRound: Boolean = true, content: @Composable () -> Unit) {
+    @Test
+    fun time_text_with_very_long_text() = verifyScreenshot {
+        val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Red)
+        val timeTextStyle = TimeTextDefaults.timeTextStyle(color = Color.Cyan)
+        val separatorStyle = TimeTextDefaults.timeTextStyle(color = Color.Yellow)
+        TimeText(
+            maxSweepAngle = 180f,
+            modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
+            timeSource = MockTimeSource,
+        ) { time ->
+            curvedText(
+                "Very long text to ensure we are respecting the maxSweep parameter",
+                CurvedModifier.weight(1f),
+                overflow = TextOverflow.Ellipsis,
+                maxSweepAngle = 180f,
+                style = customStyle
+            )
+            timeTextSeparator(separatorStyle)
+            curvedText(time, style = timeTextStyle)
+        }
+    }
+
+    @Test
+    fun time_text_with_very_long_text_smaller_angle() = verifyScreenshot {
+        val customStyle = TimeTextDefaults.timeTextStyle(color = Color.Red)
+        val timeTextStyle = TimeTextDefaults.timeTextStyle(color = Color.Cyan)
+        val separatorStyle = TimeTextDefaults.timeTextStyle(color = Color.Yellow)
+        TimeText(
+            maxSweepAngle = 90f,
+            modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
+            timeSource = MockTimeSource,
+        ) { time ->
+            curvedText(
+                "Very long text to ensure we are respecting the maxSweep parameter",
+                CurvedModifier.weight(1f),
+                overflow = TextOverflow.Ellipsis,
+                style = customStyle
+            )
+            timeTextSeparator(separatorStyle)
+            curvedText(time, style = timeTextStyle)
+        }
+    }
+
+    @Test
+    fun time_text_long_text_before_time() = TimeTextWithDefaults { time ->
+        curvedText(
+            "Very long text to ensure we are respecting the weight parameter",
+            CurvedModifier.weight(1f),
+            overflow = TextOverflow.Ellipsis
+        )
+        timeTextSeparator()
+        curvedText(time)
+        timeTextSeparator()
+        curvedText("More")
+    }
+
+    @Test
+    fun time_text_long_text_after_time() = TimeTextWithDefaults { time ->
+        curvedText("More")
+        timeTextSeparator()
+        curvedText(time)
+        timeTextSeparator()
+        curvedText(
+            "Very long text to ensure we are respecting the weight parameter",
+            CurvedModifier.weight(1f),
+            overflow = TextOverflow.Ellipsis
+        )
+    }
+
+    private fun TimeTextWithDefaults(content: CurvedScope.(String) -> Unit) = verifyScreenshot {
+        TimeText(
+            maxSweepAngle = 180f,
+            modifier = Modifier.testTag(TEST_TAG).background(Color.DarkGray),
+            timeSource = MockTimeSource,
+            content = content
+        )
+    }
+
+    private fun verifyScreenshot(content: @Composable () -> Unit) {
         rule.verifyScreenshot(
-            // Valid characters for golden identifiers are [A-Za-z0-9_-]
-            // TestParameterInjector adds '[' + parameter_values + ']' to the test name.
-            methodName = testName.methodName.replace("[", "_").replace("]", ""),
+            methodName = testName.methodName,
             screenshotRule = screenshotRule,
             content = {
                 val screenSize = LocalContext.current.resources.configuration.smallestScreenWidthDp
                 DeviceConfigurationOverride(
-                    DeviceConfigurationOverride.ForcedSize(
-                        DpSize(screenSize.dp, screenSize.dp)
-                    ) then DeviceConfigurationOverride.RoundScreen(isDeviceRound)
+                    DeviceConfigurationOverride.ForcedSize(DpSize(screenSize.dp, screenSize.dp))
                 ) {
                     content()
                 }
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextTest.kt
index f932e4a..e8a7528 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimeTextTest.kt
@@ -21,20 +21,13 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.DeviceConfigurationOverride
-import androidx.compose.ui.test.RoundScreen
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.unit.sp
 import androidx.test.filters.SdkSuppress
+import androidx.wear.compose.foundation.curvedComposable
 import java.util.Calendar
 import java.util.Locale
 import java.util.TimeZone
@@ -47,101 +40,54 @@
 
     @Test
     fun supports_testtag() {
-        rule.setContentWithTheme { TimeText(modifier = Modifier.testTag(TEST_TAG)) { time() } }
+        rule.setContentWithTheme { TimeText(modifier = Modifier.testTag(TEST_TAG)) }
 
         rule.onNodeWithTag(TEST_TAG).assertExists()
     }
 
     @Test
-    fun shows_time_by_default_on_non_round_device() {
+    fun shows_time_by_default() {
         val timeText = "time"
 
         rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                TimeText(
-                    timeSource =
-                        object : TimeSource {
-                            @Composable override fun currentTime(): String = timeText
-                        },
-                )
-            }
+            TimeText(
+                timeSource =
+                    object : TimeSource {
+                        @Composable override fun currentTime(): String = timeText
+                    },
+            )
         }
 
         // Note that onNodeWithText doesn't work for curved text, so only testing for non-round.
-        rule.onNodeWithText(timeText).assertIsDisplayed()
+        rule.onNodeWithContentDescription(timeText).assertIsDisplayed()
     }
 
     @Test
-    fun updates_clock_when_source_changes_on_non_round_device() {
+    fun updates_clock_when_source_changes() {
         val timeState = mutableStateOf("Unchanged")
 
         rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                TimeText(
-                    modifier = Modifier.testTag(TEST_TAG),
-                    timeSource =
-                        object : TimeSource {
-                            @Composable override fun currentTime(): String = timeState.value
-                        },
-                ) {
-                    time()
-                }
-            }
+            TimeText(
+                modifier = Modifier.testTag(TEST_TAG),
+                timeSource =
+                    object : TimeSource {
+                        @Composable override fun currentTime(): String = timeState.value
+                    },
+            )
         }
         timeState.value = "Changed"
-        rule.onNodeWithText("Changed").assertIsDisplayed()
-    }
-
-    @Test
-    fun updates_clock_when_source_changes_on_round_device() {
-        val timeState = mutableStateOf("Unchanged")
-
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
-                TimeText(
-                    modifier = Modifier.testTag(TEST_TAG),
-                    timeSource =
-                        object : TimeSource {
-                            @Composable override fun currentTime(): String = timeState.value
-                        },
-                ) {
-                    time()
-                }
-            }
-        }
-        timeState.value = "Changed"
-        rule.waitForIdle()
         rule.onNodeWithContentDescription("Changed").assertIsDisplayed()
     }
 
     @Test
-    fun checks_status_displayed_on_non_round_device() {
+    fun checks_status_displayed() {
         val statusText = "Status"
 
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                TimeText {
-                    text(statusText)
-                    separator()
-                    time()
-                }
-            }
-        }
-
-        rule.onNodeWithText(statusText).assertIsDisplayed()
-    }
-
-    @Test
-    fun checks_status_displayed_on_round_device() {
-        val statusText = "Status"
-
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
-                TimeText {
-                    text(statusText)
-                    separator()
-                    time()
-                }
+        rule.setContentWithTheme() {
+            TimeText { time ->
+                curvedText(statusText)
+                timeTextSeparator()
+                curvedText(time)
             }
         }
 
@@ -149,54 +95,26 @@
     }
 
     @Test
-    fun checks_separator_displayed_on_non_round_device() {
+    fun checks_separator_displayed() {
         val statusText = "Status"
         val separatorText = "·"
 
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                TimeText {
-                    text(statusText)
-                    separator()
-                    time()
-                }
-            }
-        }
-
-        rule.onNodeWithText(separatorText).assertIsDisplayed()
-    }
-
-    @Test
-    fun checks_separator_displayed_on_round_device() {
-        val statusText = "Status"
-        val separatorText = "·"
-
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
-                TimeText {
-                    text(statusText)
-                    separator()
-                    time()
-                }
-            }
-        }
+        rule.setContentWithTheme { BasicTimeTextWithStatus(statusText) }
 
         rule.onNodeWithContentDescription(separatorText).assertIsDisplayed()
     }
 
     @Test
-    fun checks_composable_displayed_on_non_round_device() {
+    fun checks_composable_displayed() {
         rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                TimeText {
-                    time()
-                    separator()
-                    composable {
-                        Text(
-                            modifier = Modifier.testTag(TEST_TAG),
-                            text = "Compose",
-                        )
-                    }
+            TimeText { time ->
+                curvedText(time)
+                timeTextSeparator()
+                curvedComposable {
+                    Text(
+                        modifier = Modifier.testTag(TEST_TAG),
+                        text = "Compose",
+                    )
                 }
             }
         }
@@ -205,178 +123,6 @@
     }
 
     @Test
-    fun checks_composable_displayed_on_round_device() {
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
-                TimeText {
-                    time()
-                    separator()
-                    composable {
-                        Text(
-                            modifier = Modifier.testTag(TEST_TAG),
-                            text = "Compose",
-                        )
-                    }
-                }
-            }
-        }
-
-        rule.onNodeWithTag(TEST_TAG).assertIsDisplayed()
-    }
-
-    @Test
-    fun changes_timeTextStyle_on_non_round_device() {
-        val timeText = "testTime"
-
-        val testTextStyle =
-            TextStyle(color = Color.Green, background = Color.Black, fontSize = 20.sp)
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                TimeText(
-                    timeSource =
-                        object : TimeSource {
-                            @Composable override fun currentTime(): String = timeText
-                        },
-                    timeTextStyle = testTextStyle
-                ) {
-                    time()
-                }
-            }
-        }
-        val actualStyle = rule.textStyleOf(timeText)
-        Assert.assertEquals(testTextStyle.color, actualStyle.color)
-        Assert.assertEquals(testTextStyle.background, actualStyle.background)
-        Assert.assertEquals(testTextStyle.fontSize, actualStyle.fontSize)
-    }
-
-    @Test
-    fun changes_material_theme_on_non_round_device_except_color() {
-        val timeText = "testTime"
-
-        val testTextStyle =
-            TextStyle(
-                color = Color.Green,
-                background = Color.Black,
-                fontStyle = FontStyle.Italic,
-                fontSize = 25.sp,
-                fontFamily = FontFamily.SansSerif
-            )
-        rule.setContent {
-            MaterialTheme(typography = MaterialTheme.typography.copy(arcMedium = testTextStyle)) {
-                DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                    TimeText(
-                        timeSource =
-                            object : TimeSource {
-                                @Composable override fun currentTime(): String = timeText
-                            },
-                    ) {
-                        time()
-                    }
-                }
-            }
-        }
-        val actualStyle = rule.textStyleOf(timeText)
-        Assert.assertEquals(testTextStyle.background, actualStyle.background)
-        Assert.assertEquals(testTextStyle.fontSize, actualStyle.fontSize)
-        Assert.assertEquals(testTextStyle.fontStyle, actualStyle.fontStyle)
-        Assert.assertEquals(testTextStyle.fontFamily, actualStyle.fontFamily)
-        Assert.assertNotEquals(testTextStyle.color, actualStyle.color)
-    }
-
-    @Test
-    fun color_remains_onBackground_when_material_theme_changed_on_non_round_device() {
-        val timeText = "testTime"
-        var onBackgroundColor = Color.Unspecified
-
-        val testTextStyle =
-            TextStyle(
-                color = Color.Green,
-                background = Color.Black,
-                fontStyle = FontStyle.Italic,
-                fontSize = 25.sp,
-                fontFamily = FontFamily.SansSerif
-            )
-        rule.setContent {
-            MaterialTheme(typography = MaterialTheme.typography.copy(labelSmall = testTextStyle)) {
-                onBackgroundColor = MaterialTheme.colorScheme.onBackground
-                DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                    TimeText(
-                        timeSource =
-                            object : TimeSource {
-                                @Composable override fun currentTime(): String = timeText
-                            },
-                    ) {
-                        time()
-                    }
-                }
-            }
-        }
-        val actualStyle = rule.textStyleOf(timeText)
-        Assert.assertEquals(onBackgroundColor, actualStyle.color)
-    }
-
-    @Test
-    fun has_correct_default_leading_text_color_on_non_round_device() {
-        val leadingText = "leadingText"
-        var primaryColor = Color.Unspecified
-
-        rule.setContentWithTheme {
-            primaryColor = MaterialTheme.colorScheme.primary
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                TimeText {
-                    text(leadingText)
-                    separator()
-                    time()
-                }
-            }
-        }
-        val actualStyle = rule.textStyleOf(leadingText)
-        Assert.assertEquals(primaryColor, actualStyle.color)
-    }
-
-    @Test
-    fun supports_custom_leading_text_color_on_non_round_device() {
-        val leadingText = "leadingText"
-        val customColor = Color.Green
-
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                TimeText(
-                    contentColor = customColor,
-                ) {
-                    text(leadingText)
-                    separator()
-                    time()
-                }
-            }
-        }
-        val actualStyle = rule.textStyleOf(leadingText)
-        Assert.assertEquals(customColor, actualStyle.color)
-    }
-
-    @Test
-    fun supports_custom_text_style_on_non_round_device() {
-        val leadingText = "leadingText"
-
-        val timeTextStyle = TextStyle(background = Color.Blue, fontSize = 14.sp)
-        val contentTextStyle =
-            TextStyle(color = Color.Green, background = Color.Black, fontSize = 20.sp)
-        rule.setContentWithTheme {
-            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
-                TimeText(contentColor = Color.Red, timeTextStyle = timeTextStyle) {
-                    text(leadingText, contentTextStyle)
-                    separator()
-                    time()
-                }
-            }
-        }
-        val actualStyle = rule.textStyleOf(leadingText)
-        Assert.assertEquals(contentTextStyle.color, actualStyle.color)
-        Assert.assertEquals(contentTextStyle.background, actualStyle.background)
-        Assert.assertEquals(contentTextStyle.fontSize, actualStyle.fontSize)
-    }
-
-    @Test
     fun formats_current_time() {
         val currentTimeInMillis = 1631544258000L // 2021-09-13 14:44:18
         val format = "HH:mm:ss"
@@ -418,4 +164,13 @@
         }
         Assert.assertEquals(expectedTime, actualTime)
     }
+
+    @Composable
+    private fun BasicTimeTextWithStatus(statusText: String) {
+        TimeText { time ->
+            curvedText(statusText)
+            timeTextSeparator()
+            curvedText(time)
+        }
+    }
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AppScaffold.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AppScaffold.kt
index edffe7e..51fc42b 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AppScaffold.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AppScaffold.kt
@@ -46,7 +46,7 @@
 @Composable
 public fun AppScaffold(
     modifier: Modifier = Modifier,
-    timeText: @Composable () -> Unit = { TimeText { time() } },
+    timeText: @Composable () -> Unit = { TimeText() },
     content: @Composable BoxScope.() -> Unit,
 ) {
     CompositionLocalProvider(
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
index f95bd3c..351a47c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimeText.kt
@@ -22,17 +22,8 @@
 import android.content.IntentFilter
 import android.text.format.DateFormat
 import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
@@ -42,17 +33,14 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.StrokeCap
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastForEach
 import androidx.wear.compose.foundation.ArcPaddingValues
 import androidx.wear.compose.foundation.CurvedAlignment
 import androidx.wear.compose.foundation.CurvedDirection
@@ -61,35 +49,29 @@
 import androidx.wear.compose.foundation.CurvedScope
 import androidx.wear.compose.foundation.CurvedTextStyle
 import androidx.wear.compose.foundation.background
-import androidx.wear.compose.foundation.curvedComposable
+import androidx.wear.compose.foundation.basicCurvedText
 import androidx.wear.compose.foundation.curvedRow
 import androidx.wear.compose.foundation.padding
 import androidx.wear.compose.foundation.sizeIn
-import androidx.wear.compose.foundation.weight
-import androidx.wear.compose.material3.TimeTextDefaults.CurvedTextSeparator
-import androidx.wear.compose.material3.TimeTextDefaults.TextSeparator
 import androidx.wear.compose.material3.TimeTextDefaults.timeFormat
+import androidx.wear.compose.material3.TimeTextDefaults.timeTextStyle
 import androidx.wear.compose.materialcore.currentTimeMillis
 import androidx.wear.compose.materialcore.is24HourFormat
-import androidx.wear.compose.materialcore.isRoundDevice
 import java.util.Calendar
 import java.util.Locale
 
 /**
- * Layout to show the current time and a label at the top of the screen. If device has a round
- * screen, then the time will be curved along the top edge of the screen, if rectangular - then the
- * text and the time will be straight.
+ * Layout to show the current time and a label, they will be drawn in a curve, following the top
+ * edge of the screen.
  *
  * Note that Wear Material UX guidance recommends that time text should not be larger than
- * [TimeTextDefaults.MaxSweepAngle] of the screen edge on round devices, which is enforced by
- * default. It is recommended that additional content, if any, is limited to short status messages
- * before the [TimeTextScope.time] using the MaterialTheme.colorScheme.primary color.
+ * [TimeTextDefaults.MaxSweepAngle] of the screen edge, which is enforced by default. It is
+ * recommended that additional content, if any, is limited to short status messages before the time
+ * using the MaterialTheme.colorScheme.primary color.
  *
  * For more information, see the
  * [Curved Text](https://developer.android.com/training/wearables/components/curved-text) guide.
  *
- * Different components of [TimeText] can be added through methods of [TimeTextScope].
- *
  * A simple [TimeText] which shows the current time:
  *
  * @sample androidx.wear.compose.material3.samples.TimeTextClockOnly
@@ -97,15 +79,19 @@
  * A [TimeText] with a short app status message shown:
  *
  * @sample androidx.wear.compose.material3.samples.TimeTextWithStatus
+ *
+ * A [TimeText] with a long status message, that needs ellipsizing:
+ *
+ * @sample androidx.wear.compose.material3.samples.TimeTextWithStatusEllipsized
  * @param modifier The modifier to be applied to the component.
  * @param curvedModifier The [CurvedModifier] used to restrict the arc in which [TimeText] is drawn.
  * @param maxSweepAngle The default maximum sweep angle in degrees.
  * @param timeSource [TimeSource] which retrieves the current time and formats it.
- * @param timeTextStyle [TextStyle] for the time text itself.
- * @param contentColor [Color] of content of displayed through [TimeTextScope.text] and
- *   [TimeTextScope.composable].
  * @param contentPadding The spacing values between the container and the content.
- * @param content The content of the [TimeText] - displays the current time by default.
+ * @param content The content of the [TimeText] - displays the current time by default. This lambda
+ *   receives the current time as a String and should display it using curvedText. Note that if long
+ *   curved text is included here, it should specify [CurvedModifier.weight] on it so that the space
+ *   available is suitably allocated.
  */
 @Composable
 public fun TimeText(
@@ -113,91 +99,31 @@
     curvedModifier: CurvedModifier = CurvedModifier,
     maxSweepAngle: Float = TimeTextDefaults.MaxSweepAngle,
     timeSource: TimeSource = TimeTextDefaults.rememberTimeSource(timeFormat()),
-    timeTextStyle: TextStyle = TimeTextDefaults.timeTextStyle(),
-    contentColor: Color = MaterialTheme.colorScheme.primary,
     contentPadding: PaddingValues = TimeTextDefaults.ContentPadding,
-    content: TimeTextScope.() -> Unit = { time() }
+    content: CurvedScope.(String) -> Unit = { time -> timeTextCurvedText(time) }
 ) {
-    val timeText = timeSource.currentTime()
+    val currentTime = timeSource.currentTime()
     val backgroundColor = CurvedTextDefaults.backgroundColor()
 
-    if (isRoundDevice()) {
-        CurvedLayout(modifier = modifier) {
-            curvedRow(
-                modifier =
-                    curvedModifier
-                        .sizeIn(maxSweepDegrees = maxSweepAngle)
-                        .padding(contentPadding.toArcPadding())
-                        .background(backgroundColor, StrokeCap.Round),
-                radialAlignment = CurvedAlignment.Radial.Center
-            ) {
-                CurvedTimeTextScope(timeText, timeTextStyle, maxSweepAngle, contentColor).apply {
-                    content()
-                    Show()
-                }
-            }
-        }
-    } else {
-        Box(modifier.fillMaxSize()) {
-            Row(
-                modifier =
-                    Modifier.align(Alignment.TopCenter)
-                        .background(backgroundColor, CircleShape)
-                        .padding(contentPadding),
-                verticalAlignment = Alignment.CenterVertically,
-                horizontalArrangement = Arrangement.Center
-            ) {
-                LinearTimeTextScope(timeText, timeTextStyle, contentColor).apply {
-                    content()
-                    Show()
-                }
-            }
+    CurvedLayout(modifier = modifier) {
+        curvedRow(
+            modifier =
+                curvedModifier
+                    .sizeIn(maxSweepDegrees = maxSweepAngle)
+                    .padding(contentPadding.toArcPadding())
+                    .background(backgroundColor, StrokeCap.Round),
+            radialAlignment = CurvedAlignment.Radial.Center
+        ) {
+            content(currentTime)
         }
     }
 }
 
-/** Receiver scope which is used by [TimeText]. */
-public sealed class TimeTextScope {
-    /**
-     * Adds a composable [Text] for non-round devices and [curvedText] for round devices to
-     * [TimeText] content. Typically used to add a short status message ahead of the time text.
-     *
-     * @param text The text to display.
-     * @param style configuration for the [text] such as color, font etc.
-     * @param weight Size the text's width proportional to its weight relative to other weighted
-     *   sibling elements in the TimeText. Specify NaN to make this text not have a weight
-     *   specified. The default value, [TimeTextDefaults.AutoTextWeight], makes this text have
-     *   weight 1f if it's the only one, and not have weight if there are two or more.
-     */
-    public abstract fun text(
-        text: String,
-        style: TextStyle? = null,
-        weight: Float = TimeTextDefaults.AutoTextWeight
-    )
-
-    /** Adds a text displaying current time. */
-    public abstract fun time()
-
-    /**
-     * Adds a separator in [TimeText].
-     *
-     * @param style configuration for the [separator] such as color, font etc.
-     */
-    public abstract fun separator(style: TextStyle? = null)
-
-    /**
-     * Adds a composable in content of [TimeText]. This can be used to display non-text information
-     * such as an icon.
-     *
-     * @param content Slot for the [composable] to be displayed.
-     */
-    public abstract fun composable(content: @Composable () -> Unit)
-}
-
 /** Contains the default values used by [TimeText]. */
 public object TimeTextDefaults {
     /** The default padding from the edge of the screen. */
     private val Padding = PaddingDefaults.edgePadding
+
     /** Default format for 24h clock. */
     public const val TimeFormat24Hours: String = "HH:mm"
 
@@ -228,8 +154,8 @@
     }
 
     /**
-     * Creates a [TextStyle] with default parameters used for showing time on square screens. By
-     * default a copy of MaterialTheme.typography.arcMedium style is created.
+     * Creates a [CurvedTextStyle] with default parameters used for showing time. By default a copy
+     * of MaterialTheme.typography.arcMedium style is created.
      *
      * @param background The background color.
      * @param color The main color.
@@ -240,9 +166,11 @@
         background: Color = Color.Unspecified,
         color: Color = MaterialTheme.colorScheme.onBackground,
         fontSize: TextUnit = TextUnit.Unspecified,
-    ): TextStyle =
-        MaterialTheme.typography.arcMedium +
-            TextStyle(color = color, background = background, fontSize = fontSize)
+    ): CurvedTextStyle =
+        CurvedTextStyle(
+            MaterialTheme.typography.arcMedium +
+                TextStyle(color = color, background = background, fontSize = fontSize)
+        )
 
     /**
      * Creates a default implementation of [TimeSource] and remembers it. Once the system time
@@ -260,48 +188,37 @@
     @Composable
     public fun rememberTimeSource(timeFormat: String): TimeSource =
         remember(timeFormat) { DefaultTimeSource(timeFormat) }
+}
 
-    /**
-     * A default implementation of Separator shown between any text/composable and the time on
-     * non-round screens.
-     *
-     * @param modifier A default modifier for the separator.
-     * @param textStyle A [TextStyle] for the separator.
-     * @param contentPadding The spacing values between the container and the separator.
-     */
-    @Composable
-    internal fun TextSeparator(
-        modifier: Modifier = Modifier,
-        textStyle: TextStyle = timeTextStyle(),
-        contentPadding: PaddingValues = PaddingValues(horizontal = 4.dp)
+/**
+ * Default curved text to use in a [TimeText], for displaying the time
+ *
+ * @param time The time to display.
+ * @param style A [CurvedTextStyle] to override the style used.
+ */
+public fun CurvedScope.timeTextCurvedText(time: String, style: CurvedTextStyle? = null) {
+    basicCurvedText(
+        time,
     ) {
-        Text(text = "·", style = textStyle, modifier = modifier.padding(contentPadding))
+        style?.let { timeTextStyle() + it } ?: timeTextStyle()
     }
+}
 
-    /**
-     * A default implementation of Separator shown between any text/composable and the time on round
-     * screens.
-     *
-     * @param curvedTextStyle A [CurvedTextStyle] for the separator.
-     * @param contentArcPadding [ArcPaddingValues] for the separator text.
-     */
-    internal fun CurvedScope.CurvedTextSeparator(
-        curvedTextStyle: CurvedTextStyle? = null,
-        contentArcPadding: ArcPaddingValues = ArcPaddingValues(angular = 4.dp)
-    ) {
-        curvedText(
-            text = "·",
-            style = curvedTextStyle,
-            modifier = CurvedModifier.padding(contentArcPadding)
-        )
-    }
-
-    /**
-     * Weight value used to specify that the value is automatic. It will be 1f when there is one
-     * text, and no weight will be used if there are 2 or more texts. For the 2+ texts case, usually
-     * one of them should have weight manually specified to ensure its properly cut and ellipsized.
-     */
-    public val AutoTextWeight: Float = -1f
+/**
+ * A default implementation of Separator, to be shown between any text/composable and the time.
+ *
+ * @param curvedTextStyle A [CurvedTextStyle] for the separator.
+ * @param contentArcPadding [ArcPaddingValues] for the separator text.
+ */
+public fun CurvedScope.timeTextSeparator(
+    curvedTextStyle: CurvedTextStyle? = null,
+    contentArcPadding: ArcPaddingValues = ArcPaddingValues(angular = 4.dp)
+) {
+    curvedText(
+        text = "·",
+        style = curvedTextStyle,
+        modifier = CurvedModifier.padding(contentArcPadding)
+    )
 }
 
 public interface TimeSource {
@@ -314,122 +231,6 @@
     @Composable public fun currentTime(): String
 }
 
-/** Implementation of [TimeTextScope] for round devices. */
-internal class CurvedTimeTextScope(
-    private val timeText: String,
-    private val timeTextStyle: TextStyle,
-    private val maxSweepAngle: Float,
-    contentColor: Color,
-) : TimeTextScope() {
-    private var textCount = 0
-    private val pending = mutableListOf<CurvedScope.() -> Unit>()
-    private val contentTextStyle = timeTextStyle.merge(contentColor)
-
-    override fun text(text: String, style: TextStyle?, weight: Float) {
-        textCount++
-        pending.add {
-            curvedText(
-                text = text,
-                overflow = TextOverflow.Ellipsis,
-                maxSweepAngle = maxSweepAngle,
-                style = CurvedTextStyle(style = contentTextStyle.merge(style)),
-                modifier =
-                    if (weight.isValidWeight()) CurvedModifier.weight(weight)
-                    // Note that we are creating a lambda here, but textCount is actually read
-                    // later, during the call to Show, when the pending list is fully constructed.
-                    else if (weight == TimeTextDefaults.AutoTextWeight && textCount <= 1)
-                        CurvedModifier.weight(1f)
-                    else CurvedModifier
-            )
-        }
-    }
-
-    override fun time() {
-        pending.add {
-            curvedText(
-                timeText,
-                maxSweepAngle = maxSweepAngle,
-                style = CurvedTextStyle(timeTextStyle)
-            )
-        }
-    }
-
-    override fun separator(style: TextStyle?) {
-        pending.add { CurvedTextSeparator(CurvedTextStyle(style = timeTextStyle.merge(style))) }
-    }
-
-    override fun composable(content: @Composable () -> Unit) {
-        pending.add {
-            curvedComposable {
-                CompositionLocalProvider(
-                    LocalContentColor provides contentTextStyle.color,
-                    LocalTextStyle provides contentTextStyle,
-                    content = content
-                )
-            }
-        }
-    }
-
-    fun CurvedScope.Show() {
-        pending.fastForEach { it() }
-    }
-}
-
-/** Implementation of [TimeTextScope] for non-round devices. */
-internal class LinearTimeTextScope(
-    private val timeText: String,
-    private val timeTextStyle: TextStyle,
-    contentColor: Color,
-) : TimeTextScope() {
-    private var textCount = 0
-    private val pending = mutableListOf<@Composable RowScope.() -> Unit>()
-    private val contentTextStyle = timeTextStyle.merge(contentColor)
-
-    override fun text(text: String, style: TextStyle?, weight: Float) {
-        textCount++
-        pending.add {
-            Text(
-                text = text,
-                maxLines = 1,
-                overflow = TextOverflow.Ellipsis,
-                style = contentTextStyle.merge(style),
-                modifier =
-                    if (weight.isValidWeight()) Modifier.weight(weight, fill = false)
-                    // Note that we are creating a lambda here, but textCount is actually read
-                    // later, during the call to Show, when the pending list is fully constructed.
-                    else if (weight == TimeTextDefaults.AutoTextWeight && textCount <= 1)
-                        Modifier.weight(1f, fill = false)
-                    else Modifier
-            )
-        }
-    }
-
-    override fun time() {
-        pending.add { Text(timeText, style = timeTextStyle) }
-    }
-
-    override fun separator(style: TextStyle?) {
-        pending.add { TextSeparator(textStyle = timeTextStyle.merge(style)) }
-    }
-
-    override fun composable(content: @Composable () -> Unit) {
-        pending.add {
-            CompositionLocalProvider(
-                LocalContentColor provides contentTextStyle.color,
-                LocalTextStyle provides contentTextStyle,
-                content = content
-            )
-        }
-    }
-
-    @Composable
-    fun RowScope.Show() {
-        pending.fastForEach { it() }
-    }
-}
-
-private fun Float.isValidWeight() = !isNaN() && this > 0f
-
 internal class DefaultTimeSource(timeFormat: String) : TimeSource {
     private val _timeFormat = timeFormat
 
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
index a674c88..685cf6d 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
@@ -109,7 +109,7 @@
         @Composable { it: @Composable () -> Unit ->
             // Only material3 demos benefit from the Material3 ScreenScaffold
             if (category.materialVersion == 3) {
-                val timeText = @Composable { androidx.wear.compose.material3.TimeText { time() } }
+                val timeText = @Composable { androidx.wear.compose.material3.TimeText() }
                 androidx.wear.compose.material3.ScreenScaffold(
                     scrollState = state,
                     timeText = remember { timeText },