Merge "Expose Capture Latency in CameraPipe API" into androidx-main
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index eadf4b8..0aa561b 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -19,7 +19,10 @@
 import android.content.ComponentCallbacks2
 import android.content.pm.ActivityInfo
 import android.content.res.Configuration
+import android.os.Build
 import android.os.Build.VERSION.SDK_INT
+import android.os.Bundle
+import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
 import androidx.compose.foundation.ScrollState
@@ -35,6 +38,8 @@
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.internal.Strings
+import androidx.compose.material3.internal.getString
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.derivedStateOf
@@ -53,6 +58,7 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.test.SemanticsMatcher
@@ -65,6 +71,7 @@
 import androidx.compose.ui.test.isDialog
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onParent
 import androidx.compose.ui.test.performClick
@@ -82,6 +89,7 @@
 import androidx.compose.ui.unit.width
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.google.common.truth.Truth.assertThat
@@ -1117,4 +1125,31 @@
         // Size entirely filled by padding provided by WindowInsetPadding
         rule.onNodeWithTag(sheetTag).onParent().assertHeightIsEqualTo(sheetHeight)
     }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun modalBottomSheet_assertSheetContentIsReadBeforeScrim() {
+        lateinit var composeView: View
+        var closeSheet = ""
+        rule.setContent {
+            closeSheet = getString(Strings.CloseSheet)
+            ModalBottomSheet(onDismissRequest = {}, modifier = Modifier.testTag(sheetTag)) {
+                composeView = LocalView.current
+                Box(Modifier.fillMaxWidth().height(sheetHeight))
+            }
+        }
+
+        val scrimViewId = rule.onNodeWithContentDescription(closeSheet).fetchSemanticsNode().id
+        val sheetViewId = rule.onNodeWithTag(sheetTag).fetchSemanticsNode().id
+
+        rule.runOnUiThread {
+            val accessibilityNodeProvider = composeView.accessibilityNodeProvider
+            val sheetViewANI = accessibilityNodeProvider.createAccessibilityNodeInfo(sheetViewId)
+            // Ensure that sheet A11y info is read before scrim view.
+            assertThat(sheetViewANI?.extras?.traversalBefore).isAtMost(scrimViewId)
+        }
+    }
+
+    private val Bundle.traversalBefore: Int
+        get() = getInt("android.view.accessibility.extra.EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL")
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
index e144c31..d1ac45a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
@@ -64,9 +64,11 @@
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.dismiss
 import androidx.compose.ui.semantics.expand
+import androidx.compose.ui.semantics.isTraversalGroup
 import androidx.compose.ui.semantics.onClick
 import androidx.compose.ui.semantics.paneTitle
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.traversalIndex
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
@@ -176,11 +178,11 @@
         },
         predictiveBackProgress = predictiveBackProgress,
     ) {
-        Box(modifier = Modifier.fillMaxSize().imePadding()) {
+        Box(modifier = Modifier.fillMaxSize().imePadding().semantics { isTraversalGroup = true }) {
             Scrim(
                 color = scrimColor,
                 onDismissRequest = animateToDismiss,
-                visible = sheetState.targetValue != Hidden
+                visible = sheetState.targetValue != Hidden,
             )
             ModalBottomSheetContent(
                 predictiveBackProgress,
@@ -277,7 +279,10 @@
                     startDragImmediately = sheetState.anchoredDraggableState.isAnimationRunning,
                     onDragStopped = { settleToDismiss(it) }
                 )
-                .semantics { paneTitle = bottomSheetPaneTitle }
+                .semantics {
+                    paneTitle = bottomSheetPaneTitle
+                    traversalIndex = 0f
+                }
                 .graphicsLayer {
                     val sheetOffset = sheetState.anchoredDraggableState.offset
                     val sheetHeight = size.height
@@ -442,6 +447,7 @@
             if (visible) {
                 Modifier.pointerInput(onDismissRequest) { detectTapGestures { onDismissRequest() } }
                     .semantics(mergeDescendants = true) {
+                        traversalIndex = 1f
                         contentDescription = closeSheet
                         onClick {
                             onDismissRequest()
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index 5b99cea..21ab6f74 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -586,7 +586,7 @@
 package androidx.compose.ui.test.internal {
 
   @SuppressCompatibility @androidx.compose.ui.test.InternalTestApi public abstract class DelayPropagatingContinuationInterceptorWrapper extends kotlin.coroutines.AbstractCoroutineContextElement implements kotlin.coroutines.ContinuationInterceptor kotlinx.coroutines.Delay {
-    ctor public DelayPropagatingContinuationInterceptorWrapper(kotlin.coroutines.ContinuationInterceptor? wrappedInterceptor);
+    ctor public DelayPropagatingContinuationInterceptorWrapper(kotlin.coroutines.ContinuationInterceptor wrappedInterceptor);
   }
 
 }
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index b1bbd4a..2f5bc6c 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -587,7 +587,7 @@
 package androidx.compose.ui.test.internal {
 
   @SuppressCompatibility @androidx.compose.ui.test.InternalTestApi public abstract class DelayPropagatingContinuationInterceptorWrapper extends kotlin.coroutines.AbstractCoroutineContextElement implements kotlin.coroutines.ContinuationInterceptor kotlinx.coroutines.Delay {
-    ctor public DelayPropagatingContinuationInterceptorWrapper(kotlin.coroutines.ContinuationInterceptor? wrappedInterceptor);
+    ctor public DelayPropagatingContinuationInterceptorWrapper(kotlin.coroutines.ContinuationInterceptor wrappedInterceptor);
   }
 
 }
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/internal/DelayPropagatingContinuationInterceptorWrapper.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/internal/DelayPropagatingContinuationInterceptorWrapper.kt
index 50c0cb7..37c58c3 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/internal/DelayPropagatingContinuationInterceptorWrapper.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/internal/DelayPropagatingContinuationInterceptorWrapper.kt
@@ -20,16 +20,14 @@
 import kotlin.coroutines.AbstractCoroutineContextElement
 import kotlin.coroutines.ContinuationInterceptor
 import kotlinx.coroutines.Delay
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.InternalCoroutinesApi
 import kotlinx.coroutines.test.TestDispatcher
 
 /**
- * A [ContinuationInterceptor] that wraps another interceptor and implements [Delay]. If the wrapped
- * interceptor also implements [Delay], the delay implementation is delegated to it, otherwise it's
- * delegated to the default delay implementation (i.e. [Dispatchers.Default]). It is necessary that
- * interceptors used in tests, with one of the [TestDispatcher]s, propagate delay like this in order
- * to work with the delay skipping that those dispatchers perform.
+ * A [ContinuationInterceptor] that wraps another interceptor and implements [Delay] by delegating
+ * to the wrapped interceptor. It is necessary that interceptors used in tests, with one of the
+ * [TestDispatcher]s, propagate delay like this in order to work with the delay skipping that those
+ * dispatchers perform.
  */
 // TODO(b/263369561): avoid InternalCoroutinesApi - it is not expected that Delay gain a method but
 // if it ever did this would have potential runtime crashes for tests. Medium term we will leave
@@ -38,10 +36,13 @@
 @OptIn(InternalCoroutinesApi::class)
 @InternalTestApi
 abstract class DelayPropagatingContinuationInterceptorWrapper(
-    wrappedInterceptor: ContinuationInterceptor?
+    wrappedInterceptor: ContinuationInterceptor
 ) :
     AbstractCoroutineContextElement(ContinuationInterceptor),
     ContinuationInterceptor,
     // Coroutines will internally use the Default dispatcher as the delay if the
     // ContinuationInterceptor does not implement Delay.
-    Delay by ((wrappedInterceptor as? Delay) ?: (Dispatchers.Default as Delay))
+    Delay by ((wrappedInterceptor as? Delay)
+        ?: error(
+            "wrappedInterceptor of DelayPropagatingContinuationInterceptorWrapper must implement Delay"
+        ))
diff --git a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt
index d77b90b..3d97309 100644
--- a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt
+++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt
@@ -31,7 +31,7 @@
  * continuation needs to be dispatched.
  */
 @OptIn(InternalTestApi::class)
-internal class FrameDeferringContinuationInterceptor(parentInterceptor: ContinuationInterceptor?) :
+internal class FrameDeferringContinuationInterceptor(parentInterceptor: ContinuationInterceptor) :
     DelayPropagatingContinuationInterceptorWrapper(parentInterceptor) {
     private val parentDispatcher = parentInterceptor as? CoroutineDispatcher
     private val toRunTrampolined = ArrayDeque<TrampolinedTask<*>>()
diff --git a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt
index 6e84196..bd1ca2b 100644
--- a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt
+++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt
@@ -39,7 +39,7 @@
  * [coroutineScope] contain the test dispatcher controlled by [delayController].
  *
  * @param coroutineScope The [CoroutineScope] used to simulate the main thread and schedule frames
- *   on. It must contain a [TestCoroutineScheduler].
+ *   on. It must contain a [TestCoroutineScheduler] and a [ContinuationInterceptor].
  * @param frameDelayNanos The number of nanoseconds to [delay] between executing frames.
  * @param onPerformTraversals Called with the frame time of the frame that was just executed, after
  *   running all `withFrameNanos` callbacks, but before resuming their callers' continuations. Any
@@ -60,9 +60,13 @@
 ) : MonotonicFrameClock {
     private val delayController =
         requireNotNull(coroutineScope.coroutineContext[TestCoroutineScheduler]) {
-            "coroutineScope should have TestCoroutineScheduler"
+            "TestMonotonicFrameClock's coroutineScope must have a TestCoroutineScheduler"
         }
-    private val parentInterceptor = coroutineScope.coroutineContext[ContinuationInterceptor]
+    // The parentInterceptor resolves to the TestDispatcher
+    private val parentInterceptor =
+        requireNotNull(coroutineScope.coroutineContext[ContinuationInterceptor]) {
+            "TestMonotonicFrameClock's coroutineScope must have a ContinuationInterceptor"
+        }
     private val lock = Any()
     private var awaiters = mutableListOf<(Long) -> Unit>()
     private var spareAwaiters = mutableListOf<(Long) -> Unit>()
diff --git a/libraryversions.toml b/libraryversions.toml
index bed356b..32eda8f 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -96,10 +96,10 @@
 MEDIA = "1.7.0-rc01"
 MEDIAROUTER = "1.8.0-alpha01"
 METRICS = "1.0.0-beta02"
-NAVIGATION = "2.8.0-beta07"
+NAVIGATION = "2.8.0-rc01"
 PAGING = "3.4.0-alpha01"
 PALETTE = "1.1.0-alpha01"
-PDF = "1.0.0-alpha01"
+PDF = "1.0.0-alpha02"
 PERCENTLAYOUT = "1.1.0-alpha01"
 PREFERENCE = "1.3.0-alpha01"
 PRINT = "1.1.0-beta01"
@@ -115,7 +115,7 @@
 RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
 REMOTECALLBACK = "1.0.0-alpha02"
 RESOURCEINSPECTION = "1.1.0-alpha01"
-ROOM = "2.7.0-alpha06"
+ROOM = "2.7.0-alpha07"
 SAFEPARCEL = "1.0.0-alpha01"
 SAVEDSTATE = "1.3.0-alpha01"
 SECURITY = "1.1.0-alpha07"
@@ -131,7 +131,7 @@
 SLICE_BUILDERS_KTX = "1.0.0-alpha09"
 SLICE_REMOTECALLBACK = "1.0.0-alpha01"
 SLIDINGPANELAYOUT = "1.3.0-alpha01"
-SQLITE = "2.5.0-alpha06"
+SQLITE = "2.5.0-alpha07"
 SQLITE_INSPECTOR = "2.1.0-alpha01"
 STABLE_AIDL = "1.0.0-alpha01"
 STARTUP = "1.2.0-alpha03"
diff --git a/lifecycle/lifecycle-runtime/proguard-rules.pro b/lifecycle/lifecycle-runtime/proguard-rules.pro
index 95192c1..4335578 100644
--- a/lifecycle/lifecycle-runtime/proguard-rules.pro
+++ b/lifecycle/lifecycle-runtime/proguard-rules.pro
@@ -15,6 +15,12 @@
     @androidx.lifecycle.OnLifecycleEvent *;
 }
 
+# The deprecated `android.app.Fragment` creates `Fragment` instances using reflection.
+# See: b/338958225, b/341537875
+-keepclasseswithmembers,allowobfuscation public class androidx.lifecycle.ReportFragment {
+    public <init>();
+}
+
 # this rule is need to work properly when app is compiled with api 28, see b/142778206
 # Also this rule prevents registerIn from being inlined.
--keepclassmembers class androidx.lifecycle.ReportFragment$LifecycleCallbacks { *; }
\ No newline at end of file
+-keepclassmembers class androidx.lifecycle.ReportFragment$LifecycleCallbacks { *; }
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index 5969262..9fa7d67 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -28,11 +28,11 @@
 
     implementation(libs.kotlinStdlib)
     api("androidx.activity:activity-compose:1.8.0")
-    api("androidx.compose.animation:animation:1.7.0-beta06")
-    implementation("androidx.compose.foundation:foundation-layout:1.7.0-beta06")
-    api("androidx.compose.runtime:runtime:1.7.0-beta06")
-    api("androidx.compose.runtime:runtime-saveable:1.7.0-beta06")
-    api("androidx.compose.ui:ui:1.7.0-beta06")
+    api("androidx.compose.animation:animation:1.7.0-rc01")
+    implementation("androidx.compose.foundation:foundation-layout:1.7.0-rc01")
+    api("androidx.compose.runtime:runtime:1.7.0-rc01")
+    api("androidx.compose.runtime:runtime-saveable:1.7.0-rc01")
+    api("androidx.compose.ui:ui:1.7.0-rc01")
     api("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
     api(projectOrArtifact(":navigation:navigation-runtime-ktx"))
     implementation(libs.kotlinSerializationCore)
diff --git a/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt b/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt
index 66ef0c3..82b13ec 100644
--- a/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt
+++ b/pdf/pdf-viewer-fragment/src/main/java/androidx/pdf/viewer/fragment/PdfViewerFragment.kt
@@ -147,7 +147,6 @@
     private var shouldRedrawOnDocumentLoaded = false
     private var isAnnotationIntentResolvable = false
     private var documentLoaded = false
-    private var isDocumentLoadedFirstTime = false
 
     /**
      * The URI of the PDF document to display defaulting to `null`.
@@ -294,9 +293,8 @@
                         shouldRedrawOnDocumentLoaded = false
                     }
                     annotationButton?.let { button ->
-                        if (isDocumentLoadedFirstTime && isAnnotationIntentResolvable) {
+                        if ((savedInstanceState == null) && isAnnotationIntentResolvable) {
                             button.visibility = View.VISIBLE
-                            isDocumentLoadedFirstTime = false
                         }
                     }
                 },
@@ -304,6 +302,9 @@
             )
 
         setUpEditFab()
+        if (savedInstanceState != null) {
+            paginatedView?.isConfigurationChanged = true
+        }
 
         // Need to adjust the view only after the layout phase is completed for the views to
         // accurately calculate the height of the view
@@ -467,6 +468,12 @@
                 val showAnnotationButton = state.getBoolean(KEY_SHOW_ANNOTATION)
                 isAnnotationIntentResolvable =
                     showAnnotationButton && findInFileView!!.visibility != View.VISIBLE
+                if (
+                    isAnnotationIntentResolvable &&
+                        state.getBoolean(KEY_ANNOTATION_BUTTON_VISIBILITY)
+                ) {
+                    annotationButton?.visibility = View.VISIBLE
+                }
             }
         }
 
@@ -621,7 +628,8 @@
             return
         }
         if (
-            annotationButton?.visibility != View.VISIBLE &&
+            isAnnotationIntentResolvable &&
+                annotationButton?.visibility != View.VISIBLE &&
                 findInFileView?.visibility != View.VISIBLE
         ) {
             annotationButton?.post {
@@ -713,6 +721,10 @@
         pdfLoaderCallbacks?.selectionModel?.let {
             outState.putParcelable(KEY_PAGE_SELECTION, it.selection().get())
         }
+        outState.putBoolean(
+            KEY_ANNOTATION_BUTTON_VISIBILITY,
+            (annotationButton?.visibility == View.VISIBLE)
+        )
     }
 
     private fun loadFile(fileUri: Uri) {
@@ -743,7 +755,6 @@
             annotationButton?.visibility = View.GONE
         }
         localUri = fileUri
-        isDocumentLoadedFirstTime = true
     }
 
     private fun validateFileUri(fileUri: Uri) {
@@ -834,5 +845,6 @@
         private const val KEY_SHOW_ANNOTATION: String = "showEditFab"
         private const val KEY_PAGE_SELECTION: String = "currentPageSelection"
         private const val KEY_DOCUMENT_URI: String = "documentUri"
+        private const val KEY_ANNOTATION_BUTTON_VISIBILITY = "isAnnotationVisible"
     }
 }
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java
index 5af4b14..0ab2bfe 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/PaginatedView.java
@@ -66,6 +66,8 @@
 
     private PageViewFactory mPageViewFactory;
 
+    private boolean mIsConfigurationChanged = false;
+
     public PaginatedView(@NonNull Context context) {
         this(context, null);
     }
@@ -444,4 +446,12 @@
                     }
                 });
     }
+
+    public void setConfigurationChanged(boolean configurationChanged) {
+        this.mIsConfigurationChanged = configurationChanged;
+    }
+
+    public boolean isConfigurationChanged() {
+        return mIsConfigurationChanged;
+    }
 }
diff --git a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java
index 037f875..2fb40ab 100644
--- a/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java
+++ b/pdf/pdf-viewer/src/main/java/androidx/pdf/viewer/ZoomScrollValueObserver.java
@@ -80,7 +80,7 @@
             mIsPageScrollingUp = false;
         }
 
-        if (mIsAnnotationIntentResolvable) {
+        if (mIsAnnotationIntentResolvable && !mPaginatedView.isConfigurationChanged()) {
 
             if (!isAnnotationButtonVisible() && position.scrollY == 0
                     && mFindInFileView.getVisibility() == View.GONE) {
@@ -108,6 +108,9 @@
                     }
                 });
             }
+        } else if (mPaginatedView.isConfigurationChanged()
+                && position.scrollY != oldPosition.scrollY) {
+            mPaginatedView.setConfigurationChanged(false);
         }
 
         if (position.scrollY > 0) {
diff --git a/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml b/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
index a7a90a4..873c2a5 100644
--- a/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
+++ b/pdf/pdf-viewer/src/main/res/layout/find_in_file.xml
@@ -63,7 +63,8 @@
         android:cropToPadding="true"
         android:padding="3dp"
         android:scaleType="centerInside"
-        android:layout_margin="5dp"/>
+        android:layout_margin="5dp"
+        android:contentDescription = "@string/previous_button_description"/>
 
     <ImageButton
         android:id="@+id/find_next_btn"
@@ -76,7 +77,7 @@
         android:padding="3dp"
         android:scaleType="centerInside"
         android:layout_margin="5dp"
-        />
+        android:contentDescription = "@string/next_button_description"/>
     <ImageButton
         android:id="@+id/close_btn"
         android:layout_width="34dp"
@@ -89,6 +90,7 @@
         android:scaleType="centerInside"
         android:layout_marginVertical="5dp"
         android:layout_marginLeft="5dp"
-        android:layout_marginRight="10dp"/>
+        android:layout_marginRight="10dp"
+        android:contentDescription = "@string/close_button_description"/>
 
 </merge>
\ No newline at end of file
diff --git a/pdf/pdf-viewer/src/main/res/values/strings.xml b/pdf/pdf-viewer/src/main/res/values/strings.xml
index 80fb57f..79cf9a2 100644
--- a/pdf/pdf-viewer/src/main/res/values/strings.xml
+++ b/pdf/pdf-viewer/src/main/res/values/strings.xml
@@ -117,6 +117,15 @@
     <!-- Hint text: Placeholder text shown in search box until the user enters query text. [CHAR LIMIT=20] -->
     <string name="hint_find">Find in file</string>
 
+    <!-- Content description for previous button in find in file menu -->
+    <string name="previous_button_description">Previous</string>
+
+    <!-- Content description for next button in find in file menu -->
+    <string name="next_button_description">Next</string>
+
+    <!-- Content description for close button in find in file menu -->
+    <string name="close_button_description">Close</string>
+
     <!-- Message for no matches found when searching for query text inside the file. [CHAR LIMIT=40] -->
     <string name="message_no_matches_found">No matches found.</string>
 
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 265b837..3642f7a 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -485,7 +485,7 @@
   }
 
   public final class PickerGroupItem {
-    ctor public PickerGroupItem(androidx.wear.compose.material3.PickerState pickerState, optional androidx.compose.ui.Modifier modifier, optional String? contentDescription, optional androidx.compose.ui.focus.FocusRequester? focusRequester, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> option);
+    ctor public PickerGroupItem(androidx.wear.compose.material3.PickerState pickerState, optional androidx.compose.ui.Modifier modifier, optional String? contentDescription, optional androidx.compose.ui.focus.FocusRequester? focusRequester, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional float spacing, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> option);
     method public String? getContentDescription();
     method public androidx.compose.ui.focus.FocusRequester? getFocusRequester();
     method public androidx.compose.ui.Modifier getModifier();
@@ -493,6 +493,7 @@
     method public kotlin.jvm.functions.Function3<androidx.wear.compose.material3.PickerScope,java.lang.Integer,java.lang.Boolean,kotlin.Unit> getOption();
     method public androidx.wear.compose.material3.PickerState getPickerState();
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? getReadOnlyLabel();
+    method public float getSpacing();
     property public final String? contentDescription;
     property public final androidx.compose.ui.focus.FocusRequester? focusRequester;
     property public final androidx.compose.ui.Modifier modifier;
@@ -500,6 +501,7 @@
     property public final kotlin.jvm.functions.Function3<androidx.wear.compose.material3.PickerScope,java.lang.Integer,java.lang.Boolean,kotlin.Unit> option;
     property public final androidx.wear.compose.material3.PickerState pickerState;
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel;
+    property public final float spacing;
   }
 
   public final class PickerGroupKt {
@@ -522,7 +524,7 @@
   }
 
   public final class PickerKt {
-    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material3.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float separation, optional @FloatRange(from=0.0, to=0.5) float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
+    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material3.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float spacing, optional @FloatRange(from=0.0, to=0.5) float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
     method @androidx.compose.runtime.Composable public static androidx.wear.compose.material3.PickerState rememberPickerState(int initialNumberOfOptions, optional int initiallySelectedOption, optional boolean repeatItems);
   }
 
@@ -1070,15 +1072,58 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
+  @androidx.compose.runtime.Immutable public final class TimePickerColors {
+    ctor public TimePickerColors(long selectedPickerContentColor, long unselectedPickerContentColor, long separatorColor, long pickerLabelColor, long confirmButtonContentColor, long confirmButtonContainerColor);
+    method public long getConfirmButtonContainerColor();
+    method public long getConfirmButtonContentColor();
+    method public long getPickerLabelColor();
+    method public long getSelectedPickerContentColor();
+    method public long getSeparatorColor();
+    method public long getUnselectedPickerContentColor();
+    property public final long confirmButtonContainerColor;
+    property public final long confirmButtonContentColor;
+    property public final long pickerLabelColor;
+    property public final long selectedPickerContentColor;
+    property public final long separatorColor;
+    property public final long unselectedPickerContentColor;
+  }
+
+  public final class TimePickerDefaults {
+    method @androidx.compose.runtime.Composable public int getTimePickerType();
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimePickerColors timePickerColors();
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimePickerColors timePickerColors(optional long selectedPickerContentColor, optional long unselectedPickerContentColor, optional long separatorColor, optional long pickerLabelColor, optional long confirmButtonContentColor, optional long confirmButtonContainerColor);
+    property @androidx.compose.runtime.Composable public final int timePickerType;
+    field public static final androidx.wear.compose.material3.TimePickerDefaults INSTANCE;
+  }
+
+  public final class TimePickerKt {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.compose.runtime.Composable public static void TimePicker(java.time.LocalTime initialTime, kotlin.jvm.functions.Function1<? super java.time.LocalTime,kotlin.Unit> onTimePicked, optional androidx.compose.ui.Modifier modifier, optional int timePickerType, optional androidx.wear.compose.material3.TimePickerColors colors);
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TimePickerType {
+    field public static final androidx.wear.compose.material3.TimePickerType.Companion Companion;
+  }
+
+  public static final class TimePickerType.Companion {
+    method public int getHoursMinutes24H();
+    method public int getHoursMinutesAmPm12H();
+    method public int getHoursMinutesSeconds24H();
+    property public final int HoursMinutes24H;
+    property public final int HoursMinutesAmPm12H;
+    property public final int HoursMinutesSeconds24H;
+  }
+
   public interface TimeSource {
     method @androidx.compose.runtime.Composable public String currentTime();
   }
 
   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;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     field public static final androidx.wear.compose.material3.TimeTextDefaults INSTANCE;
     field public static final float MaxSweepAngle = 70.0f;
@@ -1093,7 +1138,7 @@
   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);
+    method public abstract void text(String text, optional androidx.compose.ui.text.TextStyle? style, optional float weight);
     method public abstract void time();
   }
 
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 265b837..3642f7a 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -485,7 +485,7 @@
   }
 
   public final class PickerGroupItem {
-    ctor public PickerGroupItem(androidx.wear.compose.material3.PickerState pickerState, optional androidx.compose.ui.Modifier modifier, optional String? contentDescription, optional androidx.compose.ui.focus.FocusRequester? focusRequester, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> option);
+    ctor public PickerGroupItem(androidx.wear.compose.material3.PickerState pickerState, optional androidx.compose.ui.Modifier modifier, optional String? contentDescription, optional androidx.compose.ui.focus.FocusRequester? focusRequester, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional float spacing, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> option);
     method public String? getContentDescription();
     method public androidx.compose.ui.focus.FocusRequester? getFocusRequester();
     method public androidx.compose.ui.Modifier getModifier();
@@ -493,6 +493,7 @@
     method public kotlin.jvm.functions.Function3<androidx.wear.compose.material3.PickerScope,java.lang.Integer,java.lang.Boolean,kotlin.Unit> getOption();
     method public androidx.wear.compose.material3.PickerState getPickerState();
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? getReadOnlyLabel();
+    method public float getSpacing();
     property public final String? contentDescription;
     property public final androidx.compose.ui.focus.FocusRequester? focusRequester;
     property public final androidx.compose.ui.Modifier modifier;
@@ -500,6 +501,7 @@
     property public final kotlin.jvm.functions.Function3<androidx.wear.compose.material3.PickerScope,java.lang.Integer,java.lang.Boolean,kotlin.Unit> option;
     property public final androidx.wear.compose.material3.PickerState pickerState;
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel;
+    property public final float spacing;
   }
 
   public final class PickerGroupKt {
@@ -522,7 +524,7 @@
   }
 
   public final class PickerKt {
-    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material3.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float separation, optional @FloatRange(from=0.0, to=0.5) float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
+    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material3.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float spacing, optional @FloatRange(from=0.0, to=0.5) float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.rotary.RotaryScrollableBehavior? rotaryScrollableBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material3.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
     method @androidx.compose.runtime.Composable public static androidx.wear.compose.material3.PickerState rememberPickerState(int initialNumberOfOptions, optional int initiallySelectedOption, optional boolean repeatItems);
   }
 
@@ -1070,15 +1072,58 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
+  @androidx.compose.runtime.Immutable public final class TimePickerColors {
+    ctor public TimePickerColors(long selectedPickerContentColor, long unselectedPickerContentColor, long separatorColor, long pickerLabelColor, long confirmButtonContentColor, long confirmButtonContainerColor);
+    method public long getConfirmButtonContainerColor();
+    method public long getConfirmButtonContentColor();
+    method public long getPickerLabelColor();
+    method public long getSelectedPickerContentColor();
+    method public long getSeparatorColor();
+    method public long getUnselectedPickerContentColor();
+    property public final long confirmButtonContainerColor;
+    property public final long confirmButtonContentColor;
+    property public final long pickerLabelColor;
+    property public final long selectedPickerContentColor;
+    property public final long separatorColor;
+    property public final long unselectedPickerContentColor;
+  }
+
+  public final class TimePickerDefaults {
+    method @androidx.compose.runtime.Composable public int getTimePickerType();
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimePickerColors timePickerColors();
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.TimePickerColors timePickerColors(optional long selectedPickerContentColor, optional long unselectedPickerContentColor, optional long separatorColor, optional long pickerLabelColor, optional long confirmButtonContentColor, optional long confirmButtonContainerColor);
+    property @androidx.compose.runtime.Composable public final int timePickerType;
+    field public static final androidx.wear.compose.material3.TimePickerDefaults INSTANCE;
+  }
+
+  public final class TimePickerKt {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) @androidx.compose.runtime.Composable public static void TimePicker(java.time.LocalTime initialTime, kotlin.jvm.functions.Function1<? super java.time.LocalTime,kotlin.Unit> onTimePicked, optional androidx.compose.ui.Modifier modifier, optional int timePickerType, optional androidx.wear.compose.material3.TimePickerColors colors);
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TimePickerType {
+    field public static final androidx.wear.compose.material3.TimePickerType.Companion Companion;
+  }
+
+  public static final class TimePickerType.Companion {
+    method public int getHoursMinutes24H();
+    method public int getHoursMinutesAmPm12H();
+    method public int getHoursMinutesSeconds24H();
+    property public final int HoursMinutes24H;
+    property public final int HoursMinutesAmPm12H;
+    property public final int HoursMinutesSeconds24H;
+  }
+
   public interface TimeSource {
     method @androidx.compose.runtime.Composable public String currentTime();
   }
 
   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;
     property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
     field public static final androidx.wear.compose.material3.TimeTextDefaults INSTANCE;
     field public static final float MaxSweepAngle = 70.0f;
@@ -1093,7 +1138,7 @@
   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);
+    method public abstract void text(String text, optional androidx.compose.ui.text.TextStyle? style, optional float weight);
     method public abstract void time();
   }
 
diff --git a/wear/compose/compose-material3/build.gradle b/wear/compose/compose-material3/build.gradle
index 4068a7f..bc201f1 100644
--- a/wear/compose/compose-material3/build.gradle
+++ b/wear/compose/compose-material3/build.gradle
@@ -52,6 +52,7 @@
     androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
     androidTestImplementation(project(":test:screenshot:screenshot"))
+    androidTestImplementation(libs.testParameterInjector)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.truth)
     androidTestImplementation(project(":wear:compose:compose-material3-samples"))
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt
index 83f1564..7149870 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/PickerDemo.kt
@@ -16,27 +16,88 @@
 
 package androidx.wear.compose.material3.demos
 
+import android.os.Build
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import androidx.wear.compose.integration.demos.common.ComposableDemo
+import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.Icon
 import androidx.wear.compose.material3.Picker
 import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.TimePicker
+import androidx.wear.compose.material3.TimePickerType
 import androidx.wear.compose.material3.rememberPickerState
 import androidx.wear.compose.material3.samples.AutoCenteringPickerGroup
 import androidx.wear.compose.material3.samples.PickerAnimateScrollToOption
 import androidx.wear.compose.material3.samples.PickerGroupSample
 import androidx.wear.compose.material3.samples.SimplePicker
+import androidx.wear.compose.material3.samples.TimePickerSample
+import androidx.wear.compose.material3.samples.TimePickerWith12HourClockSample
+import androidx.wear.compose.material3.samples.TimePickerWithSecondsSample
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
 
 val PickerDemos =
     listOf(
+        // Requires API level 26 or higher due to java.time dependency.
+        *(if (Build.VERSION.SDK_INT >= 26)
+            arrayOf(
+                ComposableDemo("Time HH:MM:SS") { TimePickerWithSecondsSample() },
+                ComposableDemo("Time HH:MM") {
+                    var showTimePicker by remember { mutableStateOf(true) }
+                    var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+                    val formatter = DateTimeFormatter.ofPattern("HH:mm")
+                    if (showTimePicker) {
+                        TimePicker(
+                            onTimePicked = {
+                                timePickerTime = it
+                                showTimePicker = false
+                            },
+                            timePickerType = TimePickerType.HoursMinutes24H,
+                            // Initialize with last picked time on reopen
+                            initialTime = timePickerTime
+                        )
+                    } else {
+                        Column(
+                            modifier = Modifier.fillMaxSize(),
+                            verticalArrangement = Arrangement.Center,
+                            horizontalAlignment = Alignment.CenterHorizontally
+                        ) {
+                            Text("Selected Time")
+                            Spacer(Modifier.height(12.dp))
+                            Button(
+                                onClick = { showTimePicker = true },
+                                label = { Text(timePickerTime.format(formatter)) },
+                                icon = {
+                                    Icon(
+                                        imageVector = Icons.Filled.Edit,
+                                        contentDescription = "Edit"
+                                    )
+                                },
+                            )
+                        }
+                    }
+                },
+                ComposableDemo("Time 12 Hour") { TimePickerWith12HourClockSample() },
+                ComposableDemo("Time System time format") { TimePickerSample() },
+            )
+        else emptyArray<ComposableDemo>()),
         ComposableDemo("Simple Picker") { SimplePicker() },
         ComposableDemo("No gradient") { PickerWithoutGradient() },
         ComposableDemo("Animate picker change") { PickerAnimateScrollToOption() },
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PickerSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PickerSample.kt
index 771e384..adc77cc 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PickerSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/PickerSample.kt
@@ -64,7 +64,7 @@
     val contentDescription by remember { derivedStateOf { "${state.selectedOption + 1}" } }
     Picker(
         state = state,
-        separation = 4.dp,
+        spacing = 4.dp,
         contentDescription = contentDescription,
     ) {
         Button(
@@ -84,7 +84,7 @@
     Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
         Picker(
             state = state,
-            separation = 4.dp,
+            spacing = 4.dp,
             contentDescription = contentDescription,
         ) {
             Button(
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt
new file mode 100644
index 0000000..78cc200
--- /dev/null
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.TimePicker
+import androidx.wear.compose.material3.TimePickerType
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import java.time.format.FormatStyle
+
+@Sampled
+@Composable
+fun TimePickerSample() {
+    var showTimePicker by remember { mutableStateOf(true) }
+    var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+    val formatter =
+        DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
+            .withLocale(LocalConfiguration.current.locales[0])
+    if (showTimePicker) {
+        TimePicker(
+            onTimePicked = {
+                timePickerTime = it
+                showTimePicker = false
+            },
+            initialTime = timePickerTime // Initialize with last picked time on reopen
+        )
+    } else {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Text("Selected Time")
+            Spacer(Modifier.height(12.dp))
+            Button(
+                onClick = { showTimePicker = true },
+                label = { Text(timePickerTime.format(formatter)) },
+                icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
+            )
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun TimePickerWithSecondsSample() {
+    var showTimePicker by remember { mutableStateOf(true) }
+    var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+    val formatter = DateTimeFormatter.ofPattern("HH:mm:ss")
+    if (showTimePicker) {
+        TimePicker(
+            onTimePicked = {
+                timePickerTime = it
+                showTimePicker = false
+            },
+            timePickerType = TimePickerType.HoursMinutesSeconds24H,
+            initialTime = timePickerTime // Initialize with last picked time on reopen
+        )
+    } else {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Text("Selected Time")
+            Spacer(Modifier.height(12.dp))
+            Button(
+                onClick = { showTimePicker = true },
+                label = { Text(timePickerTime.format(formatter)) },
+                icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
+            )
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun TimePickerWith12HourClockSample() {
+    var showTimePicker by remember { mutableStateOf(true) }
+    var timePickerTime by remember { mutableStateOf(LocalTime.now()) }
+    val formatter = DateTimeFormatter.ofPattern("hh:mm a")
+    if (showTimePicker) {
+        TimePicker(
+            onTimePicked = {
+                timePickerTime = it
+                showTimePicker = false
+            },
+            timePickerType = TimePickerType.HoursMinutesAmPm12H,
+            initialTime = timePickerTime // Initialize with last picked time on reopen
+        )
+    } else {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Text("Selected Time")
+            Spacer(Modifier.height(12.dp))
+            Button(
+                onClick = { showTimePicker = true },
+                label = { Text(timePickerTime.format(formatter)) },
+                icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
+            )
+        }
+    }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PickerTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PickerTest.kt
index d579f24..6a66a954 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PickerTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/PickerTest.kt
@@ -212,7 +212,7 @@
                     modifier =
                         Modifier.testTag(TEST_TAG)
                             .requiredSize(itemSizeDp * 11 + separationDp * 10 * separationSign),
-                    separation = separationDp * separationSign
+                    spacing = separationDp * separationSign
                 ) {
                     Box(Modifier.requiredSize(itemSizeDp))
                 }
@@ -759,7 +759,7 @@
                         Modifier.testTag(TEST_TAG)
                             .requiredSize(pickerHeightDp)
                             .onGloballyPositioned { pickerLayoutCoordinates = it },
-                    separation = separationDp * separationSign,
+                    spacing = separationDp * separationSign,
                     readOnly = readOnly.value,
                     contentDescription = CONTENT_DESCRIPTION,
                 ) { optionIndex ->
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerScreenshotTest.kt
new file mode 100644
index 0000000..ed1aaf9
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerScreenshotTest.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.content.res.Configuration
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import java.time.LocalTime
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class TimePickerScreenshotTest {
+    @get:Rule val rule = createComposeRule()
+
+    @get:Rule val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+    @get:Rule val testName = TestName()
+
+    @Test
+    fun timePicker24h_withoutSeconds() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutes24H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+                )
+            }
+        )
+
+    @Test
+    fun timePicker24h_withSeconds() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutesSeconds24H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 37)
+                )
+            }
+        )
+
+    @Test
+    fun timePicker12h() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutesAmPm12H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+                )
+            }
+        )
+
+    @Test
+    fun timePicker24h_withoutSeconds_largeScreen() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            isLargeScreen = true,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutes24H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+                )
+            }
+        )
+
+    @Test
+    fun timePicker24h_withSeconds_largeScreen() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            isLargeScreen = true,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutesSeconds24H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 37)
+                )
+            }
+        )
+
+    @Test
+    fun timePicker12h_largeScreen() =
+        rule.verifyTimePickerScreenshot(
+            methodName = testName.methodName,
+            screenshotRule = screenshotRule,
+            isLargeScreen = true,
+            content = {
+                TimePicker(
+                    onTimePicked = {},
+                    modifier = Modifier.testTag(TEST_TAG),
+                    timePickerType = TimePickerType.HoursMinutesAmPm12H,
+                    initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+                )
+            }
+        )
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    private fun ComposeContentTestRule.verifyTimePickerScreenshot(
+        methodName: String,
+        screenshotRule: AndroidXScreenshotTestRule,
+        testTag: String = TEST_TAG,
+        isLargeScreen: Boolean = false,
+        content: @Composable () -> Unit
+    ) {
+        val screenSizeDp = if (isLargeScreen) SCREENSHOT_SIZE_LARGE else SCREENSHOT_SIZE
+        setContentWithTheme {
+            val originalConfiguration = LocalConfiguration.current
+            val fixedScreenSizeConfiguration =
+                remember(originalConfiguration) {
+                    Configuration(originalConfiguration).apply {
+                        screenWidthDp = screenSizeDp
+                        screenHeightDp = screenSizeDp
+                    }
+                }
+            CompositionLocalProvider(LocalConfiguration provides fixedScreenSizeConfiguration) {
+                Box(
+                    modifier =
+                        Modifier.size(screenSizeDp.dp)
+                            .background(MaterialTheme.colorScheme.background)
+                ) {
+                    content()
+                }
+            }
+        }
+
+        onNodeWithTag(testTag).captureToImage().assertAgainstGolden(screenshotRule, methodName)
+    }
+}
+
+private const val SCREENSHOT_SIZE = 192
+private const val SCREENSHOT_SIZE_LARGE = 228
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerTest.kt
new file mode 100644
index 0000000..9f347f0
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TimePickerTest.kt
@@ -0,0 +1,362 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.content.res.Resources
+import android.os.Build
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithContentDescription
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performScrollToIndex
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.wear.compose.material3.internal.Plurals
+import androidx.wear.compose.material3.internal.Strings
+import androidx.wear.compose.material3.samples.TimePickerSample
+import androidx.wear.compose.material3.samples.TimePickerWith12HourClockSample
+import androidx.wear.compose.material3.samples.TimePickerWithSecondsSample
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import java.time.temporal.ChronoField
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TimePickerTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun timepicker_supports_testtag() {
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                modifier = Modifier.testTag(TEST_TAG),
+                initialTime = LocalTime.now()
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun timepicker12h_supports_testtag() {
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                modifier = Modifier.testTag(TEST_TAG),
+                timePickerType = TimePickerType.HoursMinutesAmPm12H,
+                initialTime = LocalTime.now()
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun timepicker_samples_build() {
+        rule.setContentWithTheme {
+            TimePickerSample()
+            TimePickerWithSecondsSample()
+            TimePickerWith12HourClockSample()
+        }
+    }
+
+    @Test
+    fun timepicker_hhmmss_initial_state() {
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 31)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutesSeconds24H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.hour,
+                selectionMode = SelectionMode.Hour
+            )
+            .assertIsDisplayed()
+            .assertIsFocused()
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .assertIsDisplayed()
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.second,
+                selectionMode = SelectionMode.Second
+            )
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun timepicker_hhmm_initial_state() {
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutes24H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.hour,
+                selectionMode = SelectionMode.Hour
+            )
+            .assertIsDisplayed()
+            .assertIsFocused()
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun timepicker_hhmm12h_initial_state() {
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                modifier = Modifier.testTag(TEST_TAG),
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutesAmPm12H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.get(ChronoField.CLOCK_HOUR_OF_AMPM),
+                selectionMode = SelectionMode.Hour
+            )
+            .assertIsDisplayed()
+            .assertIsFocused()
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .assertIsDisplayed()
+        rule.onNodeWithText("AM", useUnmergedTree = true).assertIsDisplayed()
+        rule.onNodeWithText("PM", useUnmergedTree = true).assertIsDisplayed()
+    }
+
+    @Test
+    fun timePicker_switch_to_minutes() {
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 31)
+        rule.setContentWithTheme { TimePicker(onTimePicked = {}, initialTime = initialTime) }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .performClick()
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .assertIsFocused()
+    }
+
+    @Test
+    fun timePicker_select_hour() {
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 31)
+        val expectedHour = 9
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = {},
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutesSeconds24H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.hour,
+                selectionMode = SelectionMode.Hour
+            )
+            .performScrollToIndex(expectedHour)
+        rule.waitForIdle()
+
+        rule
+            .onNodeWithTimeValue(selectedValue = expectedHour, selectionMode = SelectionMode.Hour)
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun timePicker_hhmmss_confirmed() {
+        lateinit var confirmedTime: LocalTime
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23, /* second= */ 31)
+        val expectedTime = LocalTime.of(/* hour= */ 9, /* minute= */ 11, /* second= */ 59)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = { confirmedTime = it },
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutesSeconds24H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.hour,
+                selectionMode = SelectionMode.Hour
+            )
+            .performScrollToIndex(expectedTime.hour)
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .performScrollToIndex(expectedTime.minute)
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.second,
+                selectionMode = SelectionMode.Second
+            )
+            .performScrollToIndex(expectedTime.second)
+        rule.confirmButton().performClick()
+        rule.waitForIdle()
+
+        assertThat(confirmedTime).isEqualTo(expectedTime)
+    }
+
+    @Test
+    fun timePicker_hhmm_confirmed() {
+        lateinit var confirmedTime: LocalTime
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+        val expectedTime = LocalTime.of(/* hour= */ 9, /* minute= */ 11)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = { confirmedTime = it },
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutes24H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.hour,
+                selectionMode = SelectionMode.Hour
+            )
+            .performScrollToIndex(expectedTime.hour)
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .performScrollToIndex(expectedTime.minute)
+        rule.confirmButton().performClick()
+        rule.waitForIdle()
+
+        assertThat(confirmedTime).isEqualTo(expectedTime)
+    }
+
+    @Test
+    fun timePicker_12h_confirmed() {
+        lateinit var confirmedTime: LocalTime
+        val initialTime = LocalTime.of(/* hour= */ 14, /* minute= */ 23)
+        val expectedTime = LocalTime.of(/* hour= */ 9, /* minute= */ 11)
+        rule.setContentWithTheme {
+            TimePicker(
+                onTimePicked = { confirmedTime = it },
+                initialTime = initialTime,
+                timePickerType = TimePickerType.HoursMinutesAmPm12H
+            )
+        }
+
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.get(ChronoField.CLOCK_HOUR_OF_AMPM),
+                selectionMode = SelectionMode.Hour
+            )
+            .performScrollToIndex(expectedTime.get(ChronoField.CLOCK_HOUR_OF_AMPM) - 1)
+        rule
+            .onNodeWithTimeValue(
+                selectedValue = initialTime.minute,
+                selectionMode = SelectionMode.Minute
+            )
+            .performScrollToIndex(expectedTime.minute)
+        rule.onNodeWithContentDescription("PM").performScrollToIndex(0)
+        rule.confirmButton().performClick()
+        rule.waitForIdle()
+
+        assertThat(confirmedTime).isEqualTo(expectedTime)
+    }
+
+    private fun SemanticsNodeInteractionsProvider.onNodeWithContentDescription(
+        label: String
+    ): SemanticsNodeInteraction = onAllNodesWithContentDescription(label).onFirst()
+
+    private fun SemanticsNodeInteractionsProvider.confirmButton(): SemanticsNodeInteraction =
+        onAllNodesWithContentDescription(
+                InstrumentationRegistry.getInstrumentation()
+                    .context
+                    .resources
+                    .getString(Strings.PickerConfirmButtonContentDescription.value)
+            )
+            .onFirst()
+
+    private fun SemanticsNodeInteractionsProvider.onNodeWithTimeValue(
+        selectedValue: Int,
+        selectionMode: SelectionMode,
+    ): SemanticsNodeInteraction =
+        onAllNodesWithContentDescription(
+                contentDescriptionForValue(
+                    InstrumentationRegistry.getInstrumentation().context.resources,
+                    selectedValue,
+                    selectionMode.contentDescriptionResource
+                )
+            )
+            .onFirst()
+
+    private fun contentDescriptionForValue(
+        resources: Resources,
+        selectedValue: Int,
+        contentDescriptionResource: Plurals,
+    ): String =
+        resources.getQuantityString(contentDescriptionResource.value, selectedValue, selectedValue)
+
+    private enum class SelectionMode(val contentDescriptionResource: Plurals) {
+        Hour(Plurals.TimePickerHoursContentDescription),
+        Minute(Plurals.TimePickerMinutesContentDescription),
+        Second(Plurals.TimePickerSecondsContentDescription),
+    }
+}
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 9c9d5cb..352df46 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
@@ -33,17 +33,18 @@
 import androidx.compose.ui.test.then
 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 org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TestName
 import org.junit.runner.RunWith
 
 @MediumTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(TestParameterInjector::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
 class TimeTextScreenshotTest {
     @get:Rule val rule = createComposeRule()
@@ -217,6 +218,28 @@
         }
 
     @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),
+                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)
@@ -238,9 +261,48 @@
             }
         }
 
+    @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),
+                timeSource = MockTimeSource,
+                content = content
+            )
+        }
+
     private fun verifyScreenshot(isDeviceRound: Boolean = true, content: @Composable () -> Unit) {
         rule.verifyScreenshot(
-            methodName = testName.methodName,
+            // Valid characters for golden identifiers are [A-Za-z0-9_-]
+            // TestParameterInjector adds '[' + parameter_values + ']' to the test name.
+            methodName = testName.methodName.replace("[", "_").replace("]", ""),
             screenshotRule = screenshotRule,
             content = {
                 val screenSize = LocalContext.current.resources.configuration.smallestScreenWidthDp
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
index 70f79b2..84634d8 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ColorScheme.kt
@@ -251,6 +251,9 @@
 
     // Progress Indicator
     internal var defaultProgressIndicatorColorsCached: ProgressIndicatorColors? = null
+
+    // Picker
+    internal var defaultTimePickerColorsCached: TimePickerColors? = null
 }
 
 /**
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt
index 05a9703..e5c965c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Picker.kt
@@ -109,8 +109,8 @@
  *   semantics, which facilitates implementation of multi-picker screens.
  * @param scalingParams The parameters to configure the scaling and transparency effects for the
  *   component. See [ScalingParams].
- * @param separation The amount of separation in [Dp] between items. Can be negative, which can be
- *   useful for Text if it has plenty of whitespace.
+ * @param spacing The amount of spacing in [Dp] between items. Can be negative, which can be useful
+ *   for Text if it has plenty of whitespace.
  * @param gradientRatio The size relative to the Picker height that the top and bottom gradients
  *   take. These gradients blur the picker content on the top and bottom. The default is 0.33, so
  *   the top 1/3 and the bottom 1/3 of the picker are taken by gradients. Should be between 0.0 and
@@ -142,7 +142,7 @@
     readOnlyLabel: @Composable (BoxScope.() -> Unit)? = null,
     onSelected: () -> Unit = {},
     scalingParams: ScalingParams = PickerDefaults.scalingParams(),
-    separation: Dp = 0.dp,
+    spacing: Dp = 0.dp,
     @FloatRange(from = 0.0, to = 0.5) gradientRatio: Float = PickerDefaults.GradientRatio,
     gradientColor: Color = MaterialTheme.colorScheme.background,
     flingBehavior: FlingBehavior = PickerDefaults.flingBehavior(state),
@@ -199,7 +199,7 @@
                                         val shimHeight =
                                             (size.height -
                                                 centerItem.unadjustedSize.toFloat() -
-                                                separation.toPx()) / 2.0f
+                                                spacing.toPx()) / 2.0f
                                         drawShim(gradientColor, shimHeight)
                                     }
                                 }
@@ -229,7 +229,7 @@
             contentPadding = PaddingValues(0.dp),
             scalingParams = scalingParams,
             horizontalAlignment = Alignment.CenterHorizontally,
-            verticalArrangement = Arrangement.spacedBy(space = separation),
+            verticalArrangement = Arrangement.spacedBy(space = spacing),
             flingBehavior = flingBehavior,
             autoCentering = AutoCenteringParams(itemIndex = 0),
             userScrollEnabled = userScrollEnabled
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt
index bed5dae..855847c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/PickerGroup.kt
@@ -39,6 +39,8 @@
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
 import androidx.compose.ui.util.fastMaxOfOrNull
@@ -141,6 +143,7 @@
                     readOnlyLabel = pickerData.readOnlyLabel,
                     flingBehavior = flingBehavior,
                     onSelected = pickerData.onSelected,
+                    spacing = pickerData.spacing,
                     userScrollEnabled = !touchExplorationServicesEnabled || pickerSelected,
                     option = { optionIndex ->
                         with(pickerData) {
@@ -220,6 +223,8 @@
  * @param focusRequester Optional [FocusRequester] for the [Picker]. If not provided, a local
  *   instance of [FocusRequester] will be created to handle the focus between different pickers.
  * @param onSelected Action triggered when the [Picker] is selected by clicking.
+ * @param spacing The amount of spacing in [Dp] between items. Can be negative, which can be useful
+ *   for Text if it has plenty of whitespace.
  * @param readOnlyLabel A slot for providing a label, displayed above the selected option when the
  *   [Picker] is read-only. The label is overlaid with the currently selected option within a Box,
  *   so it is recommended that the label is given [Alignment.TopCenter].
@@ -233,6 +238,7 @@
     val focusRequester: FocusRequester? = null,
     val onSelected: () -> Unit = {},
     val readOnlyLabel: @Composable (BoxScope.() -> Unit)? = null,
+    val spacing: Dp = 0.dp,
     val option: @Composable PickerScope.(optionIndex: Int, pickerSelected: Boolean) -> Unit
 )
 
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
new file mode 100644
index 0000000..df4bb34
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
@@ -0,0 +1,762 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.collection.IntObjectMap
+import androidx.collection.MutableIntObjectMap
+import androidx.compose.animation.core.Animatable
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.focused
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.rememberTextMeasurer
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
+import androidx.wear.compose.material3.ButtonDefaults.buttonColors
+import androidx.wear.compose.material3.internal.Plurals
+import androidx.wear.compose.material3.internal.Strings
+import androidx.wear.compose.material3.internal.getPlurals
+import androidx.wear.compose.material3.internal.getString
+import androidx.wear.compose.material3.tokens.TimePickerTokens
+import androidx.wear.compose.materialcore.is24HourFormat
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import java.time.temporal.ChronoField
+
+/**
+ * A full screen TimePicker with configurable columns that allows users to select a time.
+ *
+ * This component is designed to take most/all of the screen and utilizes large fonts.
+ *
+ * Example of a [TimePicker]:
+ *
+ * @sample androidx.wear.compose.material3.samples.TimePickerSample
+ *
+ * Example of a [TimePicker] with seconds:
+ *
+ * @sample androidx.wear.compose.material3.samples.TimePickerWithSecondsSample
+ *
+ * Example of a 12 hour clock [TimePicker]:
+ *
+ * @sample androidx.wear.compose.material3.samples.TimePickerWith12HourClockSample
+ * @param initialTime The initial time to be displayed in the TimePicker. The default value is the
+ *   current time.
+ * @param onTimePicked The callback that is called when the user confirms the time selection. It
+ *   provides the selected time as [LocalTime].
+ * @param modifier Modifier to be applied to the `Box` containing the UI elements.
+ * @param timePickerType The different [TimePickerType] supported by this time picker. It indicates
+ *   whether to show seconds or AM/PM selector as well as hours and minutes.
+ * @param colors [TimePickerColors] be applied to the TimePicker.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+@Composable
+fun TimePicker(
+    initialTime: LocalTime,
+    onTimePicked: (LocalTime) -> Unit,
+    modifier: Modifier = Modifier,
+    timePickerType: TimePickerType = TimePickerDefaults.timePickerType,
+    colors: TimePickerColors = TimePickerDefaults.timePickerColors(),
+) {
+    val inspectionMode = LocalInspectionMode.current
+    val fullyDrawn = remember { Animatable(if (inspectionMode) 1f else 0f) }
+
+    val touchExplorationStateProvider = remember { DefaultTouchExplorationStateProvider() }
+    val touchExplorationServicesEnabled by touchExplorationStateProvider.touchExplorationState()
+    // When the time picker loads, none of the individual pickers are selected in talkback mode,
+    // otherwise hours picker should be focused.
+    val pickerGroupState =
+        if (touchExplorationServicesEnabled) {
+            rememberPickerGroupState(FocusableElementsTimePicker.NONE.index)
+        } else {
+            rememberPickerGroupState(FocusableElementsTimePicker.HOURS.index)
+        }
+    val focusRequesterConfirmButton = remember { FocusRequester() }
+
+    val hourString = getString(Strings.TimePickerHour)
+    val minuteString = getString(Strings.TimePickerMinute)
+
+    val is12hour = timePickerType == TimePickerType.HoursMinutesAmPm12H
+    val hourState =
+        if (is12hour) {
+            rememberPickerState(
+                initialNumberOfOptions = 12,
+                initiallySelectedOption = initialTime[ChronoField.CLOCK_HOUR_OF_AMPM] - 1,
+            )
+        } else {
+            rememberPickerState(
+                initialNumberOfOptions = 24,
+                initiallySelectedOption = initialTime.hour,
+            )
+        }
+    val minuteState =
+        rememberPickerState(
+            initialNumberOfOptions = 60,
+            initiallySelectedOption = initialTime.minute,
+        )
+
+    val hoursContentDescription =
+        createDescription(
+            pickerGroupState,
+            if (is12hour) hourState.selectedOption + 1 else hourState.selectedOption,
+            hourString,
+            Plurals.TimePickerHoursContentDescription,
+        )
+    val minutesContentDescription =
+        createDescription(
+            pickerGroupState,
+            minuteState.selectedOption,
+            minuteString,
+            Plurals.TimePickerMinutesContentDescription,
+        )
+
+    val thirdPicker = getOptionalThirdPicker(timePickerType, pickerGroupState, initialTime)
+
+    val onPickerSelected =
+        { current: FocusableElementsTimePicker, next: FocusableElementsTimePicker ->
+            if (pickerGroupState.selectedIndex != current.index) {
+                pickerGroupState.selectedIndex = current.index
+            } else {
+                pickerGroupState.selectedIndex = next.index
+                if (next == FocusableElementsTimePicker.CONFIRM_BUTTON) {
+                    focusRequesterConfirmButton.requestFocus()
+                }
+            }
+        }
+
+    Box(modifier = modifier.fillMaxSize().alpha(fullyDrawn.value)) {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            Spacer(Modifier.height(14.dp))
+            val focusedPicker = FocusableElementsTimePicker[pickerGroupState.selectedIndex]
+            FontScaleIndependent {
+                val styles = getTimePickerStyles(timePickerType, thirdPicker)
+                Text(
+                    text =
+                        when {
+                            focusedPicker == FocusableElementsTimePicker.HOURS -> hourString
+                            focusedPicker == FocusableElementsTimePicker.MINUTES -> minuteString
+                            focusedPicker == FocusableElementsTimePicker.SECONDS_OR_PERIOD &&
+                                thirdPicker != null -> thirdPicker.label
+                            else -> ""
+                        },
+                    color = colors.pickerLabelColor,
+                    style = styles.labelTextStyle,
+                    maxLines = 1,
+                    modifier =
+                        Modifier.height(24.dp)
+                            .fillMaxWidth(0.76f)
+                            .align(Alignment.CenterHorizontally),
+                    textAlign = TextAlign.Center
+                )
+                Spacer(Modifier.height(styles.sectionVerticalPadding))
+                Row(
+                    modifier = Modifier.fillMaxWidth().weight(1f),
+                    verticalAlignment = Alignment.CenterVertically,
+                    horizontalArrangement = Arrangement.Center,
+                ) {
+                    val pickerGroupItems =
+                        mutableListOf(
+                            PickerGroupItem(
+                                pickerState = hourState,
+                                modifier = Modifier.width(styles.optionWidth).fillMaxHeight(),
+                                onSelected = {
+                                    onPickerSelected(
+                                        FocusableElementsTimePicker.HOURS,
+                                        FocusableElementsTimePicker.MINUTES,
+                                    )
+                                },
+                                contentDescription = hoursContentDescription,
+                                option =
+                                    pickerTextOption(
+                                        textStyle = styles.optionTextStyle,
+                                        selectedPickerColor = colors.selectedPickerContentColor,
+                                        unselectedPickerColor = colors.unselectedPickerContentColor,
+                                        indexToText = {
+                                            "%02d".format(if (is12hour) it + 1 else it)
+                                        },
+                                        optionHeight = styles.optionHeight,
+                                    ),
+                                spacing = styles.optionSpacing
+                            ),
+                            PickerGroupItem(
+                                pickerState = minuteState,
+                                modifier = Modifier.width(styles.optionWidth).fillMaxHeight(),
+                                onSelected = {
+                                    onPickerSelected(
+                                        FocusableElementsTimePicker.MINUTES,
+                                        if (timePickerType == TimePickerType.HoursMinutes24H) {
+                                            FocusableElementsTimePicker.CONFIRM_BUTTON
+                                        } else {
+                                            FocusableElementsTimePicker.SECONDS_OR_PERIOD
+                                        }
+                                    )
+                                },
+                                contentDescription = minutesContentDescription,
+                                option =
+                                    pickerTextOption(
+                                        textStyle = styles.optionTextStyle,
+                                        indexToText = { "%02d".format(it) },
+                                        selectedPickerColor = colors.selectedPickerContentColor,
+                                        unselectedPickerColor = colors.unselectedPickerContentColor,
+                                        optionHeight = styles.optionHeight,
+                                    ),
+                                spacing = styles.optionSpacing
+                            ),
+                        )
+
+                    if (thirdPicker != null) {
+                        pickerGroupItems.add(
+                            PickerGroupItem(
+                                pickerState = thirdPicker.state,
+                                modifier = Modifier.width(styles.optionWidth).fillMaxHeight(),
+                                onSelected = {
+                                    onPickerSelected(
+                                        FocusableElementsTimePicker.SECONDS_OR_PERIOD,
+                                        FocusableElementsTimePicker.CONFIRM_BUTTON,
+                                    )
+                                },
+                                contentDescription = thirdPicker.contentDescription,
+                                option =
+                                    pickerTextOption(
+                                        textStyle = styles.optionTextStyle,
+                                        indexToText = thirdPicker.indexToText,
+                                        selectedPickerColor = colors.selectedPickerContentColor,
+                                        unselectedPickerColor = colors.unselectedPickerContentColor,
+                                        optionHeight = styles.optionHeight,
+                                    ),
+                                spacing = styles.optionSpacing
+                            ),
+                        )
+                    }
+                    PickerGroup(
+                        *pickerGroupItems.toTypedArray(),
+                        modifier = Modifier.fillMaxWidth(),
+                        pickerGroupState = pickerGroupState,
+                        separator = {
+                            Separator(
+                                textStyle = styles.optionTextStyle,
+                                color = colors.separatorColor,
+                                separatorPadding = styles.separatorPadding,
+                                text = if (it == 0 || !is12hour) ":" else ""
+                            )
+                        },
+                        autoCenter = false,
+                        touchExplorationStateProvider = touchExplorationStateProvider,
+                    )
+                }
+                Spacer(Modifier.height(styles.sectionVerticalPadding))
+            }
+            EdgeButton(
+                onClick = {
+                    val secondOrPeriodSelectedOption = thirdPicker?.state?.selectedOption ?: 0
+                    val confirmedTime =
+                        if (is12hour) {
+                            LocalTime.of(
+                                    hourState.selectedOption + 1,
+                                    minuteState.selectedOption,
+                                    0,
+                                )
+                                .with(
+                                    ChronoField.AMPM_OF_DAY,
+                                    secondOrPeriodSelectedOption.toLong()
+                                )
+                        } else {
+                            LocalTime.of(
+                                hourState.selectedOption,
+                                minuteState.selectedOption,
+                                secondOrPeriodSelectedOption,
+                            )
+                        }
+                    onTimePicked(confirmedTime)
+                },
+                modifier =
+                    Modifier.semantics {
+                            focused =
+                                pickerGroupState.selectedIndex ==
+                                    FocusableElementsTimePicker.CONFIRM_BUTTON.index
+                        }
+                        .focusRequester(focusRequesterConfirmButton)
+                        .focusable(),
+                buttonHeight = ButtonDefaults.EdgeButtonHeightSmall,
+                colors =
+                    buttonColors(
+                        contentColor = colors.confirmButtonContentColor,
+                        containerColor = colors.confirmButtonContainerColor
+                    ),
+            ) {
+                Icon(
+                    imageVector = Icons.Filled.Check,
+                    contentDescription = getString(Strings.PickerConfirmButtonContentDescription),
+                    modifier = Modifier.size(24.dp).wrapContentSize(align = Alignment.Center),
+                )
+            }
+        }
+    }
+
+    if (!inspectionMode) {
+        LaunchedEffect(Unit) { fullyDrawn.animateTo(1f) }
+    }
+}
+
+/** Specifies the types of columns to display in the TimePicker. */
+@Immutable
+@JvmInline
+value class TimePickerType internal constructor(internal val value: Int) {
+    companion object {
+        /** Displays two columns for hours (24-hour format) and minutes. */
+        val HoursMinutes24H = TimePickerType(0)
+        /** Displays three columns for hours (24-hour format), minutes and seconds. */
+        val HoursMinutesSeconds24H = TimePickerType(1)
+        /** Displays three columns for hours (12-hour format), minutes and AM/PM label. */
+        val HoursMinutesAmPm12H = TimePickerType(2)
+    }
+
+    override fun toString() =
+        when (this) {
+            HoursMinutes24H -> "HoursMinutes24H"
+            HoursMinutesSeconds24H -> "HoursMinutesSeconds24H"
+            HoursMinutesAmPm12H -> "HoursMinutesAmPm12H"
+            else -> "Unknown"
+        }
+}
+
+/** Contains the default values used by [TimePicker] */
+object TimePickerDefaults {
+
+    /** The default [TimePickerType] for [TimePicker] aligns with the current system time format. */
+    val timePickerType: TimePickerType
+        @Composable
+        get() =
+            if (is24HourFormat()) {
+                TimePickerType.HoursMinutes24H
+            } else {
+                TimePickerType.HoursMinutesAmPm12H
+            }
+
+    /** Creates a [TimePickerColors] for a [TimePicker]. */
+    @Composable fun timePickerColors() = MaterialTheme.colorScheme.defaultTimePickerColors
+
+    /**
+     * Creates a [TimePickerColors] for a [TimePicker].
+     *
+     * @param selectedPickerContentColor The content color of selected picker.
+     * @param unselectedPickerContentColor The content color of unselected pickers.
+     * @param separatorColor The color of separator between the pickers.
+     * @param pickerLabelColor The color of the picker label.
+     * @param confirmButtonContentColor The content color of the confirm button.
+     * @param confirmButtonContainerColor The container color of the confirm button.
+     */
+    @Composable
+    fun timePickerColors(
+        selectedPickerContentColor: Color = Color.Unspecified,
+        unselectedPickerContentColor: Color = Color.Unspecified,
+        separatorColor: Color = Color.Unspecified,
+        pickerLabelColor: Color = Color.Unspecified,
+        confirmButtonContentColor: Color = Color.Unspecified,
+        confirmButtonContainerColor: Color = Color.Unspecified,
+    ) =
+        MaterialTheme.colorScheme.defaultTimePickerColors.copy(
+            selectedPickerContentColor = selectedPickerContentColor,
+            unselectedPickerContentColor = unselectedPickerContentColor,
+            separatorColor = separatorColor,
+            pickerLabelColor = pickerLabelColor,
+            confirmButtonContentColor = confirmButtonContentColor,
+            confirmButtonContainerColor = confirmButtonContainerColor,
+        )
+
+    private val ColorScheme.defaultTimePickerColors: TimePickerColors
+        get() {
+            return defaultTimePickerColorsCached
+                ?: TimePickerColors(
+                        selectedPickerContentColor =
+                            fromToken(TimePickerTokens.SelectedPickerContentColor),
+                        unselectedPickerContentColor =
+                            fromToken(TimePickerTokens.UnselectedPickerContentColor),
+                        separatorColor = fromToken(TimePickerTokens.SeparatorColor),
+                        pickerLabelColor = fromToken(TimePickerTokens.PickerLabelColor),
+                        confirmButtonContentColor =
+                            fromToken(TimePickerTokens.ConfirmButtonContentColor),
+                        confirmButtonContainerColor =
+                            fromToken(TimePickerTokens.ConfirmButtonContainerColor),
+                    )
+                    .also { defaultTimePickerColorsCached = it }
+        }
+}
+
+/**
+ * Represents the colors used by a [TimePicker].
+ *
+ * @param selectedPickerContentColor The content color of selected picker.
+ * @param unselectedPickerContentColor The content color of unselected pickers.
+ * @param separatorColor The color of separator between the pickers.
+ * @param pickerLabelColor The color of the picker label.
+ * @param confirmButtonContentColor The content color of the confirm button.
+ * @param confirmButtonContainerColor The container color of the confirm button.
+ */
+@Immutable
+class TimePickerColors
+constructor(
+    val selectedPickerContentColor: Color,
+    val unselectedPickerContentColor: Color,
+    val separatorColor: Color,
+    val pickerLabelColor: Color,
+    val confirmButtonContentColor: Color,
+    val confirmButtonContainerColor: Color,
+) {
+    internal fun copy(
+        selectedPickerContentColor: Color,
+        unselectedPickerContentColor: Color,
+        separatorColor: Color,
+        pickerLabelColor: Color,
+        confirmButtonContentColor: Color,
+        confirmButtonContainerColor: Color,
+    ) =
+        TimePickerColors(
+            selectedPickerContentColor =
+                selectedPickerContentColor.takeOrElse { this.selectedPickerContentColor },
+            unselectedPickerContentColor =
+                unselectedPickerContentColor.takeOrElse { this.unselectedPickerContentColor },
+            separatorColor = separatorColor.takeOrElse { this.separatorColor },
+            pickerLabelColor = pickerLabelColor.takeOrElse { this.pickerLabelColor },
+            confirmButtonContentColor =
+                confirmButtonContentColor.takeOrElse { this.confirmButtonContentColor },
+            confirmButtonContainerColor =
+                confirmButtonContainerColor.takeOrElse { this.confirmButtonContainerColor },
+        )
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || other !is TimePickerColors) return false
+
+        if (selectedPickerContentColor != other.selectedPickerContentColor) return false
+        if (unselectedPickerContentColor != other.unselectedPickerContentColor) return false
+        if (separatorColor != other.separatorColor) return false
+        if (pickerLabelColor != other.pickerLabelColor) return false
+        if (confirmButtonContentColor != other.confirmButtonContentColor) return false
+        if (confirmButtonContainerColor != other.confirmButtonContainerColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = selectedPickerContentColor.hashCode()
+        result = 31 * result + unselectedPickerContentColor.hashCode()
+        result = 31 * result + separatorColor.hashCode()
+        result = 31 * result + pickerLabelColor.hashCode()
+        result = 31 * result + confirmButtonContentColor.hashCode()
+        result = 31 * result + confirmButtonContainerColor.hashCode()
+
+        return result
+    }
+}
+
+@Composable
+private fun getTimePickerStyles(
+    timePickerType: TimePickerType,
+    optionalThirdPicker: PickerData?
+): TimePickerStyles {
+    val isLargeScreen = LocalConfiguration.current.screenWidthDp > 225
+    val labelTextStyle =
+        if (isLargeScreen) {
+                TimePickerTokens.PickerLabelLargeTypography
+            } else {
+                TimePickerTokens.PickerLabelTypography
+            }
+            .value
+
+    val optionTextStyle =
+        if (isLargeScreen || timePickerType == TimePickerType.HoursMinutes24H) {
+                TimePickerTokens.PickerContentLargeTypography
+            } else {
+                TimePickerTokens.PickerContentTypography
+            }
+            .value
+            .copy(textAlign = TextAlign.Center)
+
+    val optionHeight =
+        if (isLargeScreen || timePickerType == TimePickerType.HoursMinutes24H) {
+            40.dp
+        } else {
+            30.dp
+        }
+    val optionSpacing = if (isLargeScreen) 6.dp else 4.dp
+    val separatorPadding =
+        when {
+            timePickerType == TimePickerType.HoursMinutes24H && isLargeScreen -> 12.dp
+            timePickerType == TimePickerType.HoursMinutes24H && !isLargeScreen -> 8.dp
+            timePickerType == TimePickerType.HoursMinutesAmPm12H && isLargeScreen -> 0.dp
+            isLargeScreen -> 6.dp
+            else -> 2.dp
+        }
+
+    val measurer = rememberTextMeasurer()
+    val density = LocalDensity.current
+    val indexToText = optionalThirdPicker?.indexToText ?: { "" }
+
+    val (twoDigitsWidth, textLabelWidth) =
+        remember(
+            density.density,
+            LocalConfiguration.current.screenWidthDp,
+        ) {
+            val mm =
+                measurer.measure(
+                    "0123456789\n${indexToText(0)}\n${indexToText(1)}",
+                    style = optionTextStyle,
+                    density = density,
+                )
+
+            (0..9).maxOf { mm.getBoundingBox(it).width } * 2 to
+                (1..2).maxOf { mm.getLineRight(it) - mm.getLineLeft(it) }
+        }
+    val measuredOptionWidth =
+        with(LocalDensity.current) {
+            if (timePickerType == TimePickerType.HoursMinutesAmPm12H) {
+                max(twoDigitsWidth.toDp(), textLabelWidth.toDp())
+            } else {
+                twoDigitsWidth.toDp()
+            } + 1.dp // Add 1dp buffer to compensate for potential conversion loss
+        }
+
+    return TimePickerStyles(
+        labelTextStyle = labelTextStyle,
+        optionTextStyle = optionTextStyle,
+        optionWidth = max(measuredOptionWidth, minimumInteractiveComponentSize),
+        optionHeight = optionHeight,
+        optionSpacing = optionSpacing,
+        separatorPadding = separatorPadding,
+        sectionVerticalPadding = if (isLargeScreen) 6.dp else 4.dp
+    )
+}
+
+/* Returns the picker data for the third column (AM/PM or seconds) based on the time picker type. */
+@RequiresApi(Build.VERSION_CODES.O)
+@Composable
+private fun getOptionalThirdPicker(
+    timePickerType: TimePickerType,
+    pickerGroupState: PickerGroupState,
+    time: LocalTime
+): PickerData? =
+    when (timePickerType) {
+        TimePickerType.HoursMinutesSeconds24H -> {
+            val secondString = getString(Strings.TimePickerSecond)
+            val secondState =
+                rememberPickerState(
+                    initialNumberOfOptions = 60,
+                    initiallySelectedOption = time.second,
+                )
+            val secondsContentDescription =
+                createDescription(
+                    pickerGroupState,
+                    secondState.selectedOption,
+                    secondString,
+                    Plurals.TimePickerSecondsContentDescription,
+                )
+            PickerData(
+                state = secondState,
+                contentDescription = secondsContentDescription,
+                label = secondString,
+                indexToText = { "%02d".format(it) }
+            )
+        }
+        TimePickerType.HoursMinutesAmPm12H -> {
+            val periodString = getString(Strings.TimePickerPeriod)
+            val periodState =
+                rememberPickerState(
+                    initialNumberOfOptions = 2,
+                    initiallySelectedOption = time[ChronoField.AMPM_OF_DAY],
+                    repeatItems = false,
+                )
+            val primaryLocale = LocalConfiguration.current.locales[0]
+            val (amString, pmString) =
+                remember(primaryLocale) {
+                    DateTimeFormatter.ofPattern("a", primaryLocale).let { formatter ->
+                        LocalTime.of(0, 0).format(formatter) to
+                            LocalTime.of(12, 0).format(formatter)
+                    }
+                }
+            val periodContentDescription by
+                remember(
+                    pickerGroupState.selectedIndex,
+                    periodState.selectedOption,
+                ) {
+                    derivedStateOf {
+                        if (
+                            pickerGroupState.selectedIndex == FocusableElementsTimePicker.NONE.index
+                        ) {
+                            periodString
+                        } else if (periodState.selectedOption == 0) {
+                            amString
+                        } else {
+                            pmString
+                        }
+                    }
+                }
+            PickerData(
+                state = periodState,
+                contentDescription = periodContentDescription,
+                label = "",
+                indexToText = { if (it == 0) amString else pmString }
+            )
+        }
+        else -> null
+    }
+
+private class PickerData(
+    val state: PickerState,
+    val contentDescription: String,
+    val label: String,
+    val indexToText: (Int) -> String,
+)
+
+private class TimePickerStyles(
+    val labelTextStyle: TextStyle,
+    val optionTextStyle: TextStyle,
+    val optionWidth: Dp,
+    val optionHeight: Dp,
+    val optionSpacing: Dp,
+    val separatorPadding: Dp,
+    val sectionVerticalPadding: Dp,
+)
+
+@Composable
+private fun Separator(
+    textStyle: TextStyle,
+    color: Color,
+    modifier: Modifier = Modifier,
+    separatorPadding: Dp,
+    text: String = ":",
+) {
+    Box(modifier = Modifier.padding(horizontal = separatorPadding)) {
+        Text(
+            text = text,
+            style = textStyle,
+            color = color,
+            modifier = modifier.width(12.dp).clearAndSetSemantics {},
+        )
+    }
+}
+
+private fun pickerTextOption(
+    textStyle: TextStyle,
+    selectedPickerColor: Color,
+    unselectedPickerColor: Color,
+    indexToText: (Int) -> String,
+    optionHeight: Dp,
+): (@Composable PickerScope.(optionIndex: Int, pickerSelected: Boolean) -> Unit) =
+    { value: Int, pickerSelected: Boolean ->
+        Box(
+            modifier = Modifier.fillMaxSize().height(optionHeight),
+            contentAlignment = Alignment.Center
+        ) {
+            Text(
+                text = indexToText(value),
+                maxLines = 1,
+                style = textStyle,
+                color =
+                    if (pickerSelected) {
+                        selectedPickerColor
+                    } else {
+                        unselectedPickerColor
+                    },
+                modifier = Modifier.align(Alignment.Center).wrapContentSize(),
+            )
+        }
+    }
+
+@Composable
+private fun createDescription(
+    pickerGroupState: PickerGroupState,
+    selectedValue: Int,
+    label: String,
+    plurals: Plurals,
+) =
+    when (pickerGroupState.selectedIndex) {
+        FocusableElementsTimePicker.NONE.index -> label
+        else -> getPlurals(plurals, selectedValue, selectedValue)
+    }
+
+@Composable
+private fun FontScaleIndependent(content: @Composable () -> Unit) {
+    CompositionLocalProvider(
+        value =
+            LocalDensity provides
+                Density(
+                    density = LocalDensity.current.density,
+                    fontScale = 1f,
+                ),
+        content = content
+    )
+}
+
+private enum class FocusableElementsTimePicker(val index: Int) {
+    HOURS(0),
+    MINUTES(1),
+    SECONDS_OR_PERIOD(2),
+    CONFIRM_BUTTON(3),
+    NONE(-1),
+    ;
+
+    companion object {
+        private val map: IntObjectMap<FocusableElementsTimePicker> =
+            MutableIntObjectMap<FocusableElementsTimePicker>().apply {
+                values().forEach { put(it.index, it) }
+            }
+
+        operator fun get(value: Int) = map[value]
+    }
+}
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 2151332..39436f5 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
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.layout.Arrangement
 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.runtime.Composable
@@ -125,8 +126,10 @@
                         .sizeIn(maxSweepDegrees = maxSweepAngle)
                         .padding(contentPadding.toArcPadding())
             ) {
-                CurvedTimeTextScope(this, timeText, timeTextStyle, maxSweepAngle, contentColor)
-                    .content()
+                CurvedTimeTextScope(timeText, timeTextStyle, maxSweepAngle, contentColor).apply {
+                    content()
+                    Show()
+                }
             }
         }
     } else {
@@ -151,8 +154,16 @@
      *
      * @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.
      */
-    abstract fun text(text: String, style: TextStyle? = null)
+    abstract fun text(
+        text: String,
+        style: TextStyle? = null,
+        weight: Float = TimeTextDefaults.AutoTextWeight
+    )
 
     /** Adds a text displaying current time. */
     abstract fun time()
@@ -277,6 +288,13 @@
             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.
+     */
+    val AutoTextWeight = -1f
 }
 
 interface TimeSource {
@@ -291,46 +309,63 @@
 
 /** Implementation of [TimeTextScope] for round devices. */
 internal class CurvedTimeTextScope(
-    private val scope: CurvedScope,
     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?) {
-        scope.curvedText(
-            text = text,
-            overflow = TextOverflow.Ellipsis,
-            maxSweepAngle = maxSweepAngle,
-            style = CurvedTextStyle(style = contentTextStyle.merge(style)),
-            modifier = CurvedModifier.weight(1f)
-        )
+    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() {
-        scope.curvedText(
-            timeText,
-            maxSweepAngle = maxSweepAngle,
-            style = CurvedTextStyle(timeTextStyle)
-        )
+        pending.add {
+            curvedText(
+                timeText,
+                maxSweepAngle = maxSweepAngle,
+                style = CurvedTextStyle(timeTextStyle)
+            )
+        }
     }
 
     override fun separator(style: TextStyle?) {
-        scope.CurvedTextSeparator(CurvedTextStyle(style = timeTextStyle.merge(style)))
+        pending.add { CurvedTextSeparator(CurvedTextStyle(style = timeTextStyle.merge(style))) }
     }
 
     override fun composable(content: @Composable () -> Unit) {
-        scope.curvedComposable {
-            CompositionLocalProvider(
-                LocalContentColor provides contentTextStyle.color,
-                LocalTextStyle provides contentTextStyle,
-                content = content
-            )
+        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. */
@@ -339,11 +374,27 @@
     private val timeTextStyle: TextStyle,
     contentColor: Color,
 ) : TimeTextScope() {
-    private val pending = mutableListOf<@Composable () -> Unit>()
+    private var textCount = 0
+    private val pending = mutableListOf<@Composable RowScope.() -> Unit>()
     private val contentTextStyle = timeTextStyle.merge(contentColor)
 
-    override fun text(text: String, style: TextStyle?) {
-        pending.add { Text(text = text, style = contentTextStyle.merge(style)) }
+    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)
+                    // 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)
+                    else Modifier
+            )
+        }
     }
 
     override fun time() {
@@ -365,11 +416,13 @@
     }
 
     @Composable
-    fun Show() {
+    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/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
new file mode 100644
index 0000000..4edf6fd
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Strings.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.internal
+
+import androidx.annotation.PluralsRes
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.res.pluralStringResource
+import androidx.compose.ui.res.stringResource
+import androidx.wear.compose.material3.R
+
+@Composable
+@ReadOnlyComposable
+internal fun getString(string: Strings): String {
+    return stringResource(string.value)
+}
+
+@Composable
+@ReadOnlyComposable
+internal fun getString(string: Strings, vararg formatArgs: Any): String {
+    return stringResource(string.value, *formatArgs)
+}
+
+@Composable
+@ReadOnlyComposable
+internal fun getPlurals(plurals: Plurals, quantity: Int): String {
+    return pluralStringResource(plurals.value, quantity)
+}
+
+@Composable
+@ReadOnlyComposable
+internal fun getPlurals(plurals: Plurals, quantity: Int, vararg formatArgs: Any): String {
+    return pluralStringResource(plurals.value, quantity, *formatArgs)
+}
+
+@JvmInline
+@Immutable
+internal value class Strings(@StringRes val value: Int) {
+    companion object {
+        inline val TimePickerHour
+            get() = Strings(R.string.wear_m3c_time_picker_hour)
+
+        inline val TimePickerMinute
+            get() = Strings(R.string.wear_m3c_time_picker_minute)
+
+        inline val TimePickerSecond
+            get() = Strings(R.string.wear_m3c_time_picker_second)
+
+        inline val TimePickerPeriod
+            get() = Strings(R.string.wear_m3c_time_picker_period)
+
+        inline val PickerConfirmButtonContentDescription
+            get() = Strings(R.string.wear_m3c_picker_confirm_button_content_description)
+    }
+}
+
+@JvmInline
+@Immutable
+internal value class Plurals(@PluralsRes val value: Int) {
+    companion object {
+        inline val TimePickerHoursContentDescription
+            get() = Plurals(R.plurals.wear_m3c_time_picker_hours_content_description)
+
+        inline val TimePickerMinutesContentDescription
+            get() = Plurals(R.plurals.wear_m3c_time_picker_minutes_content_description)
+
+        inline val TimePickerSecondsContentDescription
+            get() = Plurals(R.plurals.wear_m3c_time_picker_seconds_content_description)
+    }
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TimePickerTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TimePickerTokens.kt
new file mode 100644
index 0000000..8700fc6
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TimePickerTokens.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.tokens
+
+internal object TimePickerTokens {
+    val SelectedPickerContentColor = ColorSchemeKeyTokens.OnBackground
+    val UnselectedPickerContentColor = ColorSchemeKeyTokens.SecondaryDim
+    val SeparatorColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val PickerLabelColor = ColorSchemeKeyTokens.Primary
+    val ConfirmButtonContentColor = ColorSchemeKeyTokens.OnPrimary
+    val ConfirmButtonContainerColor = ColorSchemeKeyTokens.PrimaryDim
+
+    val PickerLabelLargeTypography = TypographyKeyTokens.TitleLarge
+    val PickerLabelTypography = TypographyKeyTokens.TitleMedium
+    val PickerContentLargeTypography = TypographyKeyTokens.NumeralMedium
+    val PickerContentTypography = TypographyKeyTokens.NumeralSmall
+}
diff --git a/wear/compose/compose-material3/src/main/res/values/strings.xml b/wear/compose/compose-material3/src/main/res/values/strings.xml
new file mode 100644
index 0000000..35e6bd7
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/res/values/strings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <string description="Lets the user know that the time unit being changed is hour. Appears on the top of the TimePicker component. [CHAR_LIMIT=8]" name="wear_m3c_time_picker_hour">Hour</string>
+    <string description="Lets the user know that the time unit being changed is minute. Appears on the top of the TimePicker component. [CHAR_LIMIT=8]" name="wear_m3c_time_picker_minute">Minute</string>
+    <string description="Lets the user know that the time unit being changed is second. Appears on the top of the TimePicker component. [CHAR_LIMIT=8]" name="wear_m3c_time_picker_second">Second</string>
+    <plurals description="Content description of the current number of hours being selected in TimePicker component. [CHAR_LIMIT=NONE]" name="wear_m3c_time_picker_hours_content_description">
+        <item quantity="one">%d Hour</item>
+        <item quantity="other">%d Hours</item>
+    </plurals>
+    <plurals description="Content description of the current number of hours being selected in TimePicker component. [CHAR_LIMIT=NONE]" name="wear_m3c_time_picker_minutes_content_description">
+        <item quantity="one">%d Minute</item>
+        <item quantity="other">%d Minutes</item>
+    </plurals>
+    <plurals description="Content description of the current number of hours being selected in TimePicker component. [CHAR_LIMIT=NONE]" name="wear_m3c_time_picker_seconds_content_description">
+        <item quantity="one">%d Second</item>
+        <item quantity="other">%d Seconds</item>
+    </plurals>
+    <string description="Content description of the period picker in TimePickerWith12HourClock. It lets the user select the period for 12H time format. [CHAR_LIMIT=NONE]" name="wear_m3c_time_picker_period">Period</string>
+    <string description="Content description of the confirm button of DatePicker and TimePicker components. It lets the user confirm the date or time selected. [CHAR_LIMIT=NONE]" name="wear_m3c_picker_confirm_button_content_description">Confirm</string>
+</resources>
\ No newline at end of file