Merge "Update custom slider thumb + label samples so that the text inside the label will scale based on the display / text size of the device." into androidx-main
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapter.kt
index eeab41c..2d55801 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapter.kt
@@ -54,6 +54,12 @@
         CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
     ] == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
 
+    /**
+     * Maps [CaptureConfig] to [Request].
+     *
+     * @throws IllegalStateException When CaptureConfig does not have any surface or a CaptureConfig
+     *          surface is not recognized in [UseCaseGraphConfig.surfaceToStreamMap]
+     */
     @OptIn(ExperimentalGetImage::class)
     fun mapToRequest(
         captureConfig: CaptureConfig,
@@ -68,7 +74,7 @@
 
         val streamIdList = surfaces.map {
             checkNotNull(useCaseGraphConfig.surfaceToStreamMap[it]) {
-                "Attempted to issue a capture with an unrecognized surface."
+                "Attempted to issue a capture with an unrecognized surface: $it"
             }
         }
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
index 19d4488..2f747c1 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
@@ -404,46 +404,66 @@
         }
         debug { "CapturePipeline#submitRequestInternal; Submitting $configs with CameraPipe" }
         val deferredList = mutableListOf<CompletableDeferred<Void?>>()
-        val requests = configs.map {
+        val requests = configs.mapNotNull {
             val completeSignal = CompletableDeferred<Void?>().also { deferredList.add(it) }
-            configAdapter.mapToRequest(
-                it, requestTemplate, sessionConfigOptions,
-                listOf(object : Request.Listener {
-                    override fun onAborted(request: Request) {
-                        completeSignal.completeExceptionally(
-                            ImageCaptureException(
-                                ERROR_CAMERA_CLOSED,
-                                "Capture request is cancelled because camera is closed",
-                                null
+            try {
+                configAdapter.mapToRequest(
+                    it, requestTemplate, sessionConfigOptions,
+                    listOf(object : Request.Listener {
+                        override fun onAborted(request: Request) {
+                            completeSignal.completeExceptionally(
+                                ImageCaptureException(
+                                    ERROR_CAMERA_CLOSED,
+                                    "Capture request is cancelled because camera is closed",
+                                    null
+                                )
                             )
-                        )
-                    }
+                        }
 
-                    override fun onTotalCaptureResult(
-                        requestMetadata: RequestMetadata,
-                        frameNumber: FrameNumber,
-                        totalCaptureResult: FrameInfo,
-                    ) {
-                        completeSignal.complete(null)
-                    }
+                        override fun onTotalCaptureResult(
+                            requestMetadata: RequestMetadata,
+                            frameNumber: FrameNumber,
+                            totalCaptureResult: FrameInfo,
+                        ) {
+                            completeSignal.complete(null)
+                        }
 
-                    @SuppressLint("ClassVerificationFailure")
-                    override fun onFailed(
-                        requestMetadata: RequestMetadata,
-                        frameNumber: FrameNumber,
-                        requestFailure: RequestFailure
-                    ) {
-                        completeSignal.completeExceptionally(
-                            ImageCaptureException(
-                                ERROR_CAPTURE_FAILED,
-                                "Capture request failed with reason " +
-                                    requestFailure.reason,
-                                null
+                        @SuppressLint("ClassVerificationFailure")
+                        override fun onFailed(
+                            requestMetadata: RequestMetadata,
+                            frameNumber: FrameNumber,
+                            requestFailure: RequestFailure
+                        ) {
+                            completeSignal.completeExceptionally(
+                                ImageCaptureException(
+                                    ERROR_CAPTURE_FAILED,
+                                    "Capture request failed with reason " +
+                                        requestFailure.reason,
+                                    null
+                                )
                             )
-                        )
-                    }
-                })
-            )
+                        }
+                    })
+                )
+            } catch (e: IllegalStateException) {
+                info(e) {
+                    "CapturePipeline#submitRequestInternal: configAdapter.mapToRequest failed!"
+                }
+                completeSignal.completeExceptionally(
+                    ImageCaptureException(
+                        ERROR_CAPTURE_FAILED,
+                        "Capture request failed with reason " + e.message,
+                        e
+                    )
+                )
+                null
+            }
+        }
+
+        if (requests.isEmpty()) {
+            // requests can be empty due to configAdapter.mapToRequest throwing exception, all the
+            // deferred instances in the list should already be completed exceptionally.
+            return deferredList
         }
 
         threads.sequentialScope.launch {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index 8703b53..162e92b 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.params.OutputConfiguration
 import android.hardware.camera2.params.SessionConfiguration.SESSION_HIGH_SPEED
 import android.hardware.camera2.params.SessionConfiguration.SESSION_REGULAR
 import android.media.MediaCodec
@@ -54,6 +55,7 @@
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.core.DynamicRange
+import androidx.camera.core.MirrorMode
 import androidx.camera.core.UseCase
 import androidx.camera.core.impl.CameraControlInternal
 import androidx.camera.core.impl.CameraInfoInternal
@@ -702,6 +704,7 @@
                     val deferrableSurface = outputConfig.surface
                     val physicalCameraId =
                         physicalCameraIdForAllStreams ?: outputConfig.physicalCameraId
+                    val mirrorMode = outputConfig.mirrorMode
                     val outputStreamConfig = OutputStream.Config.create(
                         size = deferrableSurface.prescribedSize,
                         format = StreamFormat(deferrableSurface.prescribedStreamFormat),
@@ -710,6 +713,15 @@
                         } else {
                             CameraId.fromCamera2Id(physicalCameraId)
                         },
+                        // No need to map MIRROR_MODE_ON_FRONT_ONLY to MIRROR_MODE_AUTO
+                        // since its default value in framework
+                        mirrorMode = when (mirrorMode) {
+                            MirrorMode.MIRROR_MODE_OFF -> OutputStream.MirrorMode(
+                                OutputConfiguration.MIRROR_MODE_NONE)
+                            MirrorMode.MIRROR_MODE_ON -> OutputStream.MirrorMode(
+                                OutputConfiguration.MIRROR_MODE_H)
+                            else -> null
+                        },
                         streamUseCase = getStreamUseCase(
                             deferrableSurface,
                             sessionConfigAdapter.surfaceToStreamUseCaseMap
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
index c0d2b91..548f95db 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
@@ -497,12 +497,10 @@
     val maxImages: Int
     val format: StreamFormat
 
-    // TODO: b/330594328 - Remove `format` and make `streamFormat` required.
     class Config(
         val stream: CameraStream.Config,
         val maxImages: Int,
-        var format: Int? = null,
-        var streamFormat: StreamFormat? = null
+        var streamFormat: StreamFormat
     )
 }
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
index d2b595e..dab85d0 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
@@ -154,11 +154,10 @@
             }
         }
         inputs = graphConfig.input?.map {
-            val streamFormat = it.streamFormat ?: StreamFormat(checkNotNull(it.format))
             InputStreamImpl(
                 nextInputId(),
                 it.maxImages,
-                streamFormat,
+                it.streamFormat,
             )
         } ?: emptyList()
 
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
index 5be543a..b0e3164 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
@@ -23,6 +23,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.params.DynamicRangeProfiles;
+import android.hardware.camera2.params.OutputConfiguration;
 import android.os.Build;
 import android.view.Surface;
 
@@ -44,6 +45,7 @@
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.DynamicRange;
 import androidx.camera.core.Logger;
+import androidx.camera.core.MirrorMode;
 import androidx.camera.core.impl.CameraCaptureCallback;
 import androidx.camera.core.impl.CaptureConfig;
 import androidx.camera.core.impl.DeferrableSurface;
@@ -391,6 +393,14 @@
                     outputConfig.getPhysicalCameraId());
         }
 
+        // No need to map MIRROR_MODE_ON_FRONT_ONLY to MIRROR_MODE_AUTO
+        // since its default value in framework
+        if (outputConfig.getMirrorMode() == MirrorMode.MIRROR_MODE_OFF) {
+            outputConfiguration.setMirrorMode(OutputConfiguration.MIRROR_MODE_NONE);
+        } else if (outputConfig.getMirrorMode() == MirrorMode.MIRROR_MODE_ON) {
+            outputConfiguration.setMirrorMode(OutputConfiguration.MIRROR_MODE_H);
+        }
+
         if (!outputConfig.getSharedSurfaces().isEmpty()) {
             outputConfiguration.enableSurfaceSharing();
             for (DeferrableSurface sharedDeferSurface : outputConfig.getSharedSurfaces()) {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompat.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompat.java
index 9503797..a445d3f 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompat.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompat.java
@@ -200,6 +200,24 @@
     }
 
     /**
+     * Returns mirror mode of {@link OutputConfiguration}.
+     * @return {@link OutputConfiguration#getMirrorMode()}
+     * @see OutputConfiguration#getMirrorMode()
+     */
+    public int getMirrorMode() {
+        return mImpl.getMirrorMode();
+    }
+
+    /**
+     * Sets mirror mode of {@link OutputConfiguration}.
+     * @param mirrorMode mirror mode to set for {@link OutputConfiguration}.
+     * @see OutputConfiguration#setMirrorMode(int)
+     */
+    public void setMirrorMode(int mirrorMode) {
+        mImpl.setMirrorMode(mirrorMode);
+    }
+
+    /**
      * Retrieve the physical camera ID set by {@link #setPhysicalCameraId(String)}.
      *
      */
@@ -488,6 +506,10 @@
     interface OutputConfigurationCompatImpl {
         void enableSurfaceSharing();
 
+        int getMirrorMode();
+
+        void setMirrorMode(int mirrorMode);
+
         @Nullable
         String getPhysicalCameraId();
 
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatApi33Impl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatApi33Impl.java
index a2d905b..bc8829f 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatApi33Impl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatApi33Impl.java
@@ -48,6 +48,16 @@
     }
 
     @Override
+    public int getMirrorMode() {
+        return ((OutputConfiguration) getOutputConfiguration()).getMirrorMode();
+    }
+
+    @Override
+    public void setMirrorMode(int mirrorMode) {
+        ((OutputConfiguration) getOutputConfiguration()).setMirrorMode(mirrorMode);
+    }
+
+    @Override
     public long getDynamicRangeProfile() {
         return ((OutputConfiguration) getOutputConfiguration()).getDynamicRangeProfile();
     }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatBaseImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatBaseImpl.java
index ef70b78..07ee1df 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatBaseImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatBaseImpl.java
@@ -19,6 +19,7 @@
 import android.annotation.SuppressLint;
 import android.graphics.ImageFormat;
 import android.hardware.camera2.params.DynamicRangeProfiles;
+import android.hardware.camera2.params.OutputConfiguration;
 import android.os.Build;
 import android.util.Size;
 import android.view.Surface;
@@ -82,6 +83,16 @@
         return ((OutputConfigurationParamsApi21) mObject).mPhysicalCameraId;
     }
 
+    @Override
+    public void setMirrorMode(int mirrorMode) {
+        //No-op
+    }
+
+    @Override
+    public int getMirrorMode() {
+        return OutputConfiguration.MIRROR_MODE_AUTO;
+    }
+
     /**
      * Set stream use case for this OutputConfiguration.
      */
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatTest.java
index 7b2ad2a..4dca498 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatTest.java
@@ -16,6 +16,9 @@
 
 package androidx.camera.camera2.internal.compat.params;
 
+import static android.hardware.camera2.params.OutputConfiguration.MIRROR_MODE_H;
+import static android.hardware.camera2.params.OutputConfiguration.MIRROR_MODE_NONE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -240,6 +243,22 @@
     }
 
     @Test
+    @Config(minSdk = 33)
+    public void canSetMirrorMode() {
+        OutputConfiguration outputConfig = mock(OutputConfiguration.class);
+
+        OutputConfigurationCompat outputConfigCompat = OutputConfigurationCompat.wrap(outputConfig);
+
+        outputConfigCompat.setMirrorMode(MIRROR_MODE_NONE);
+
+        verify(outputConfig, times(1)).setMirrorMode(MIRROR_MODE_NONE);
+
+        outputConfigCompat.setMirrorMode(MIRROR_MODE_H);
+
+        verify(outputConfig, times(1)).setMirrorMode(MIRROR_MODE_H);
+    }
+
+    @Test
     public void canSetDynamicRangeProfile() {
         OutputConfigurationCompat outputConfigCompat =
                 new OutputConfigurationCompat(mock(Surface.class));
diff --git a/camera/camera-core/api/1.4.0-beta01.txt b/camera/camera-core/api/1.4.0-beta01.txt
index c84401f..058ffa8 100644
--- a/camera/camera-core/api/1.4.0-beta01.txt
+++ b/camera/camera-core/api/1.4.0-beta01.txt
@@ -212,6 +212,9 @@
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
   }
 
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalMirrorMode {
+  }
+
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
   }
 
@@ -507,6 +510,7 @@
     ctor public Preview.Builder();
     method public androidx.camera.core.Preview build();
     method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalMirrorMode public androidx.camera.core.Preview.Builder setMirrorMode(int);
     method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
     method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
     method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index c84401f..058ffa8 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -212,6 +212,9 @@
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
   }
 
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalMirrorMode {
+  }
+
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
   }
 
@@ -507,6 +510,7 @@
     ctor public Preview.Builder();
     method public androidx.camera.core.Preview build();
     method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalMirrorMode public androidx.camera.core.Preview.Builder setMirrorMode(int);
     method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
     method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
     method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
diff --git a/camera/camera-core/api/restricted_1.4.0-beta01.txt b/camera/camera-core/api/restricted_1.4.0-beta01.txt
index c84401f..058ffa8 100644
--- a/camera/camera-core/api/restricted_1.4.0-beta01.txt
+++ b/camera/camera-core/api/restricted_1.4.0-beta01.txt
@@ -212,6 +212,9 @@
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
   }
 
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalMirrorMode {
+  }
+
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
   }
 
@@ -507,6 +510,7 @@
     ctor public Preview.Builder();
     method public androidx.camera.core.Preview build();
     method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalMirrorMode public androidx.camera.core.Preview.Builder setMirrorMode(int);
     method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
     method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
     method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index c84401f..058ffa8 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -212,6 +212,9 @@
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
   }
 
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalMirrorMode {
+  }
+
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
   }
 
@@ -507,6 +510,7 @@
     ctor public Preview.Builder();
     method public androidx.camera.core.Preview build();
     method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalMirrorMode public androidx.camera.core.Preview.Builder setMirrorMode(int);
     method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
     method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
     method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/UseCaseTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/UseCaseTest.kt
index f288885..e8a9867 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/UseCaseTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/UseCaseTest.kt
@@ -25,6 +25,7 @@
 import androidx.camera.core.MirrorMode.MIRROR_MODE_OFF
 import androidx.camera.core.MirrorMode.MIRROR_MODE_ON
 import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
+import androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED
 import androidx.camera.core.UseCase.snapToSurfaceRotation
 import androidx.camera.core.concurrent.CameraCoordinator
 import androidx.camera.core.impl.Config
@@ -265,7 +266,7 @@
     @Test
     fun defaultMirrorModeIsOff() {
         val fakeUseCase = createFakeUseCase()
-        assertThat(fakeUseCase.mirrorModeInternal).isEqualTo(MIRROR_MODE_OFF)
+        assertThat(fakeUseCase.mirrorModeInternal).isEqualTo(MIRROR_MODE_UNSPECIFIED)
     }
 
     @Test
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalMirrorMode.java b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalMirrorMode.java
new file mode 100644
index 0000000..5e841b7
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalMirrorMode.java
@@ -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.camera.core;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.RequiresOptIn;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Denotes that the annotated API is designed to be experimental for {@link MirrorMode}
+ */
+@Retention(CLASS)
+@RequiresOptIn
+public @interface ExperimentalMirrorMode {
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index 798ba62..2cf9086 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -423,7 +423,10 @@
 
         sessionConfigBuilder.setExpectedFrameRateRange(streamSpec.getExpectedFrameRateRange());
 
-        sessionConfigBuilder.addSurface(mDeferrableSurface, streamSpec.getDynamicRange(), null);
+        sessionConfigBuilder.addSurface(mDeferrableSurface,
+                streamSpec.getDynamicRange(),
+                null,
+                MirrorMode.MIRROR_MODE_UNSPECIFIED);
 
         sessionConfigBuilder.addErrorListener((sessionConfig, error) -> {
             clearPipeline();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java b/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java
index 49af138..cb2e328 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java
@@ -42,12 +42,16 @@
      */
     public static final int MIRROR_MODE_ON_FRONT_ONLY = 2;
 
+    /** The mirror mode is not specified by the user **/
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final int MIRROR_MODE_UNSPECIFIED = -1;
+
     private MirrorMode() {
     }
 
     /**
      */
-    @IntDef({MIRROR_MODE_OFF, MIRROR_MODE_ON, MIRROR_MODE_ON_FRONT_ONLY})
+    @IntDef({MIRROR_MODE_OFF, MIRROR_MODE_ON, MIRROR_MODE_ON_FRONT_ONLY, MIRROR_MODE_UNSPECIFIED})
     @Retention(RetentionPolicy.SOURCE)
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public @interface Mirror {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index 2163ecb..04f330d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -18,6 +18,7 @@
 
 import static androidx.camera.core.CameraEffect.PREVIEW;
 import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
+import static androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED;
 import static androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
 import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_DYNAMIC_RANGE;
 import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_FORMAT;
@@ -54,6 +55,7 @@
 import android.graphics.SurfaceTexture;
 import android.media.ImageReader;
 import android.media.MediaCodec;
+import android.os.Build;
 import android.util.Pair;
 import android.util.Range;
 import android.util.Size;
@@ -332,7 +334,8 @@
         if (mSurfaceProvider != null) {
             sessionConfigBuilder.addSurface(mSessionDeferrableSurface,
                     streamSpec.getDynamicRange(),
-                    getPhysicalCameraId());
+                    getPhysicalCameraId(),
+                    getMirrorModeInternal());
         }
 
         sessionConfigBuilder.addErrorListener((sessionConfig, error) -> {
@@ -846,7 +849,11 @@
 
             setCaptureType(UseCaseConfigFactory.CaptureType.PREVIEW);
             setTargetClass(Preview.class);
-            mutableConfig.insertOption(OPTION_MIRROR_MODE, Defaults.DEFAULT_MIRROR_MODE);
+
+            if (mutableConfig.retrieveOption(
+                    OPTION_MIRROR_MODE, MIRROR_MODE_UNSPECIFIED) == MIRROR_MODE_UNSPECIFIED) {
+                mutableConfig.insertOption(OPTION_MIRROR_MODE, Defaults.DEFAULT_MIRROR_MODE);
+            }
         }
 
         /**
@@ -1024,13 +1031,30 @@
         }
 
         /**
-         * setMirrorMode is not supported on Preview.
+         * Sets the mirror mode.
+         *
+         * <p>Valid values include: {@link MirrorMode#MIRROR_MODE_OFF},
+         * {@link MirrorMode#MIRROR_MODE_ON} and {@link MirrorMode#MIRROR_MODE_ON_FRONT_ONLY}.
+         * If not set, it defaults to {@link MirrorMode#MIRROR_MODE_ON_FRONT_ONLY}.
+         *
+         * <p>For API 33 and above, it will change the mirroring behavior for Preview use case.
+         * It is calling
+         * {@link android.hardware.camera2.params.OutputConfiguration#setMirrorMode(int)}.
+         *
+         * <p> For API 32 and below, it will be no-op.
+         *
+         * @param mirrorMode The mirror mode of the intended target.
+         * @return The current Builder.
+         * @see android.hardware.camera2.params.OutputConfiguration#setMirrorMode(int)
          */
-        @RestrictTo(Scope.LIBRARY_GROUP)
+        @ExperimentalMirrorMode
         @NonNull
         @Override
         public Builder setMirrorMode(@MirrorMode.Mirror int mirrorMode) {
-            throw new UnsupportedOperationException("setMirrorMode is not supported.");
+            if (Build.VERSION.SDK_INT >= 33) {
+                getMutableConfig().insertOption(OPTION_MIRROR_MODE, mirrorMode);
+            }
+            return this;
         }
 
         /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
index eb703aa..5918040 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
@@ -19,6 +19,7 @@
 import static androidx.camera.core.MirrorMode.MIRROR_MODE_OFF;
 import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON;
 import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
+import static androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED;
 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_MAX_RESOLUTION;
 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_RESOLUTION_SELECTOR;
 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO;
@@ -442,7 +443,7 @@
     @RestrictTo(Scope.LIBRARY_GROUP)
     @MirrorMode.Mirror
     protected int getMirrorModeInternal() {
-        return ((ImageOutputConfig) mCurrentConfig).getMirrorMode(MIRROR_MODE_OFF);
+        return ((ImageOutputConfig) mCurrentConfig).getMirrorMode(MIRROR_MODE_UNSPECIFIED);
     }
 
     /**
@@ -453,6 +454,7 @@
     public boolean isMirroringRequired(@NonNull CameraInternal camera) {
         int mirrorMode = getMirrorModeInternal();
         switch (mirrorMode) {
+            case MIRROR_MODE_UNSPECIFIED:
             case MIRROR_MODE_OFF:
                 return false;
             case MIRROR_MODE_ON:
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
index 414ddb0..6ea58a0 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
@@ -30,6 +30,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.DynamicRange;
 import androidx.camera.core.Logger;
+import androidx.camera.core.MirrorMode;
 import androidx.camera.core.impl.stabilization.StabilizationMode;
 import androidx.camera.core.internal.compat.workaround.SurfaceSorter;
 
@@ -109,6 +110,14 @@
         public abstract String getPhysicalCameraId();
 
         /**
+         * Returns the mirror mode.
+         *
+         * @return {@link MirrorMode}
+         */
+        @MirrorMode.Mirror
+        public abstract int getMirrorMode();
+
+        /**
          * Returns the surface group ID. Default value is {@link #SURFACE_GROUP_ID_NONE} meaning
          * it doesn't belong to any surface group. A surface group ID is used to identify which
          * surface group this output surface belongs to. Output streams with the same
@@ -137,6 +146,7 @@
                     .setSurface(surface)
                     .setSharedSurfaces(Collections.emptyList())
                     .setPhysicalCameraId(null)
+                    .setMirrorMode(MirrorMode.MIRROR_MODE_UNSPECIFIED)
                     .setSurfaceGroupId(SURFACE_GROUP_ID_NONE)
                     .setDynamicRange(DynamicRange.SDR);
         }
@@ -168,6 +178,14 @@
             public abstract Builder setPhysicalCameraId(@Nullable String cameraId);
 
             /**
+             * Sets the mirror mode. It specifies mirroring mode for
+             * {@link android.hardware.camera2.params.OutputConfiguration}.
+             * @see android.hardware.camera2.params.OutputConfiguration#setMirrorMode(int)
+             */
+            @NonNull
+            public abstract Builder setMirrorMode(@MirrorMode.Mirror int mirrorMode);
+
+            /**
              * Sets the surface group ID. A surface group ID is used to identify which surface group
              * this output surface belongs to. Output streams with the same non-negative group ID
              * won't receive the camera output simultaneously therefore it could be used to reduce
@@ -643,11 +661,13 @@
          * Add a surface to the set that the session repeatedly writes data to.
          *
          * <p>The dynamic range of this surface will default to {@link DynamicRange#SDR}. To
-         * manually set the dynamic range, use {@link #addSurface(DeferrableSurface, DynamicRange)}.
+         * manually set the dynamic range, use
+         * {@link #addSurface(DeferrableSurface, DynamicRange, String, int)}.
          */
         @NonNull
         public Builder addSurface(@NonNull DeferrableSurface surface) {
-            return addSurface(surface, DynamicRange.SDR, null);
+            return addSurface(surface, DynamicRange.SDR, null,
+                    MirrorMode.MIRROR_MODE_UNSPECIFIED);
         }
 
         /**
@@ -657,10 +677,12 @@
         @NonNull
         public Builder addSurface(@NonNull DeferrableSurface surface,
                 @NonNull DynamicRange dynamicRange,
-                @Nullable String physicalCameraId) {
+                @Nullable String physicalCameraId,
+                @MirrorMode.Mirror int mirrorMode) {
             OutputConfig outputConfig = OutputConfig.builder(surface)
                     .setPhysicalCameraId(physicalCameraId)
                     .setDynamicRange(dynamicRange)
+                    .setMirrorMode(mirrorMode)
                     .build();
             mOutputConfigs.add(outputConfig);
             mCaptureConfigBuilder.addSurface(surface);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
index d690404..b98833a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
@@ -45,6 +45,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.camera.core.CameraEffect;
 import androidx.camera.core.ImageCapture;
+import androidx.camera.core.MirrorMode;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.camera.core.impl.CameraInternal;
@@ -277,7 +278,10 @@
 
         propagateChildrenCamera2Interop(streamSpec.getResolution(), builder);
 
-        builder.addSurface(mCameraEdge.getDeferrableSurface(), streamSpec.getDynamicRange(), null);
+        builder.addSurface(mCameraEdge.getDeferrableSurface(),
+                streamSpec.getDynamicRange(),
+                null,
+                MirrorMode.MIRROR_MODE_UNSPECIFIED);
         builder.addRepeatingCameraCaptureCallback(
                 mVirtualCameraAdapter.getParentMetadataCallback());
         if (streamSpec.getImplementationOptions() != null) {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
index 0469f62..380be3b 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
@@ -16,8 +16,8 @@
 
 package androidx.camera.core;
 
-import static androidx.camera.core.MirrorMode.MIRROR_MODE_OFF;
 import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
+import static androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -172,7 +172,7 @@
     @Test
     public void defaultMirrorModeIsOff() {
         ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
-        assertThat(imageAnalysis.getMirrorModeInternal()).isEqualTo(MIRROR_MODE_OFF);
+        assertThat(imageAnalysis.getMirrorModeInternal()).isEqualTo(MIRROR_MODE_UNSPECIFIED);
     }
 
     @Test(expected = UnsupportedOperationException.class)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
index bc1b60a..affae0b 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
@@ -33,8 +33,8 @@
 import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
 import androidx.camera.core.ImageCapture.OUTPUT_FORMAT_JPEG
 import androidx.camera.core.ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR
-import androidx.camera.core.MirrorMode.MIRROR_MODE_OFF
 import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
+import androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED
 import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.CaptureConfig
 import androidx.camera.core.impl.ImageCaptureConfig
@@ -211,7 +211,7 @@
     @Test
     fun defaultMirrorModeIsOff() {
         val imageCapture = ImageCapture.Builder().build()
-        assertThat(imageCapture.mirrorModeInternal).isEqualTo(MIRROR_MODE_OFF)
+        assertThat(imageCapture.mirrorModeInternal).isEqualTo(MIRROR_MODE_UNSPECIFIED)
     }
 
     @Test(expected = UnsupportedOperationException::class)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 7ab1e17..f4e6f6b 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -34,6 +34,8 @@
 import androidx.camera.core.CameraEffect.PREVIEW
 import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
 import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
+import androidx.camera.core.MirrorMode.MIRROR_MODE_OFF
+import androidx.camera.core.MirrorMode.MIRROR_MODE_ON
 import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
 import androidx.camera.core.Preview.SurfaceProvider
 import androidx.camera.core.SurfaceRequest.TransformationInfo
@@ -279,9 +281,34 @@
         assertThat(preview.mirrorModeInternal).isEqualTo(MIRROR_MODE_ON_FRONT_ONLY)
     }
 
-    @Test(expected = UnsupportedOperationException::class)
-    fun setMirrorMode_throwException() {
-        Preview.Builder().setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
+    @Config(minSdk = 33)
+    @Test
+    fun setMirrorMode_OnFrontOnly() {
+        val preview = createPreview()
+        assertThat(preview.mirrorModeInternal).isEqualTo(MIRROR_MODE_ON_FRONT_ONLY)
+
+        val sessionConfig = preview.sessionConfig
+        assertThat(sessionConfig.outputConfigs[0].mirrorMode).isEqualTo(MIRROR_MODE_ON_FRONT_ONLY)
+    }
+
+    @Config(minSdk = 33)
+    @Test
+    fun setMirrorMode_On() {
+        val preview = createPreview(mirrorMode = MIRROR_MODE_ON)
+        assertThat(preview.mirrorModeInternal).isEqualTo(MIRROR_MODE_ON)
+
+        val sessionConfig = preview.sessionConfig
+        assertThat(sessionConfig.outputConfigs[0].mirrorMode).isEqualTo(MIRROR_MODE_ON)
+    }
+
+    @Config(minSdk = 33)
+    @Test
+    fun setMirrorMode_Off() {
+        val preview = createPreview(mirrorMode = MIRROR_MODE_OFF)
+        assertThat(preview.mirrorModeInternal).isEqualTo(MIRROR_MODE_OFF)
+
+        val sessionConfig = preview.sessionConfig
+        assertThat(sessionConfig.outputConfigs[0].mirrorMode).isEqualTo(MIRROR_MODE_OFF)
     }
 
     @Test
@@ -833,9 +860,11 @@
         camera: FakeCamera = backCamera,
         targetRotation: Int = ROTATION_90,
         surfaceProvider: SurfaceProvider = SurfaceProvider {
-        }
+        },
+        mirrorMode: Int = MirrorMode.MIRROR_MODE_UNSPECIFIED
     ): Preview {
         previewToDetach = Preview.Builder()
+            .setMirrorMode(mirrorMode)
             .setTargetRotation(targetRotation)
             .build()
         previewToDetach.effect = effect
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index 95971f2..dcdc96a 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -356,7 +356,11 @@
      */
     @MirrorMode.Mirror
     public int getMirrorMode() {
-        return getMirrorModeInternal();
+        int mirrorMode = getMirrorModeInternal();
+        if (mirrorMode == MirrorMode.MIRROR_MODE_UNSPECIFIED) {
+            return MirrorMode.MIRROR_MODE_OFF;
+        }
+        return mirrorMode;
     }
 
     @SuppressWarnings("unchecked")
@@ -903,7 +907,10 @@
         DynamicRange dynamicRange = streamSpec.getDynamicRange();
         if (!isStreamError && mDeferrableSurface != null) {
             if (isStreamActive) {
-                sessionConfigBuilder.addSurface(mDeferrableSurface, dynamicRange, null);
+                sessionConfigBuilder.addSurface(mDeferrableSurface,
+                        dynamicRange,
+                        null,
+                        MirrorMode.MIRROR_MODE_UNSPECIFIED);
             } else {
                 sessionConfigBuilder.addNonRepeatingSurface(mDeferrableSurface, dynamicRange);
             }
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
index e64e886..5421e2f 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
@@ -46,8 +46,10 @@
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.ConcurrentCamera;
 import androidx.camera.core.ConcurrentCamera.SingleCameraConfig;
+import androidx.camera.core.ExperimentalMirrorMode;
 import androidx.camera.core.FocusMeteringAction;
 import androidx.camera.core.MeteringPoint;
+import androidx.camera.core.MirrorMode;
 import androidx.camera.core.Preview;
 import androidx.camera.core.UseCaseGroup;
 import androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration;
@@ -274,7 +276,7 @@
     }
 
     @SuppressLint("NullAnnotationGroup")
-    @OptIn(markerClass = {ExperimentalCamera2Interop.class,
+    @OptIn(markerClass = {ExperimentalCamera2Interop.class, ExperimentalMirrorMode.class,
             androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop.class})
     private void bindToLifecycleForConcurrentCamera(
             @NonNull ProcessCameraProvider cameraProvider,
@@ -329,6 +331,7 @@
                             .build(),
                     lifecycleOwner);
             Preview previewBack = new Preview.Builder()
+                    .setMirrorMode(MirrorMode.MIRROR_MODE_OFF)
                     .build();
             previewBack.setSurfaceProvider(backPreviewView.getSurfaceProvider());
             SingleCameraConfig secondary = new SingleCameraConfig(
diff --git a/compose/material3/material3/api/1.3.0-beta01.txt b/compose/material3/material3/api/1.3.0-beta01.txt
index f92b5a9..40db5a9 100644
--- a/compose/material3/material3/api/1.3.0-beta01.txt
+++ b/compose/material3/material3/api/1.3.0-beta01.txt
@@ -2012,8 +2012,6 @@
   }
 
   public final class TooltipKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -2035,6 +2033,11 @@
     property public abstract androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> transition;
   }
 
+  public final class Tooltip_androidKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
+  }
+
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
     ctor public TopAppBarColors(long containerColor, long scrolledContainerColor, long navigationIconContentColor, long titleContentColor, long actionIconContentColor);
     method public androidx.compose.material3.TopAppBarColors copy(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index f92b5a9..40db5a9 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -2012,8 +2012,6 @@
   }
 
   public final class TooltipKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -2035,6 +2033,11 @@
     property public abstract androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> transition;
   }
 
+  public final class Tooltip_androidKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
+  }
+
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
     ctor public TopAppBarColors(long containerColor, long scrolledContainerColor, long navigationIconContentColor, long titleContentColor, long actionIconContentColor);
     method public androidx.compose.material3.TopAppBarColors copy(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
diff --git a/compose/material3/material3/api/restricted_1.3.0-beta01.txt b/compose/material3/material3/api/restricted_1.3.0-beta01.txt
index f92b5a9..40db5a9 100644
--- a/compose/material3/material3/api/restricted_1.3.0-beta01.txt
+++ b/compose/material3/material3/api/restricted_1.3.0-beta01.txt
@@ -2012,8 +2012,6 @@
   }
 
   public final class TooltipKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -2035,6 +2033,11 @@
     property public abstract androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> transition;
   }
 
+  public final class Tooltip_androidKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
+  }
+
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
     ctor public TopAppBarColors(long containerColor, long scrolledContainerColor, long navigationIconContentColor, long titleContentColor, long actionIconContentColor);
     method public androidx.compose.material3.TopAppBarColors copy(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index f92b5a9..40db5a9 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -2012,8 +2012,6 @@
   }
 
   public final class TooltipKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -2035,6 +2033,11 @@
     property public abstract androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> transition;
   }
 
+  public final class Tooltip_androidKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
+  }
+
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
     ctor public TopAppBarColors(long containerColor, long scrolledContainerColor, long navigationIconContentColor, long titleContentColor, long actionIconContentColor);
     method public androidx.compose.material3.TopAppBarColors copy(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
index b42e087..8777756 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
@@ -16,12 +16,320 @@
 
 package androidx.compose.material3
 
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.material3.tokens.PlainTooltipTokens
+import androidx.compose.material3.tokens.RichTooltipTokens
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.CacheDrawScope
+import androidx.compose.ui.draw.DrawResult
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.isSpecified
 
+/**
+ * Plain tooltip that provides a descriptive message.
+ *
+ * Usually used with [TooltipBox].
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param caretSize [DpSize] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Please see [TooltipDefaults.caretSize] to
+ * see the default dimensions. Pass in Dp.Unspecified for this parameter if no caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param contentColor [Color] that will be applied to the tooltip's content.
+ * @param containerColor [Color] that will be applied to the tooltip's container.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param content the composable that will be used to populate the tooltip's content.
+ */
 @Composable
-internal actual fun windowContainerWidthInPx(): Int = with(LocalDensity.current) {
-    LocalConfiguration.current.screenWidthDp.dp.roundToPx()
+@ExperimentalMaterial3Api
+actual fun TooltipScope.PlainTooltip(
+    modifier: Modifier,
+    caretSize: DpSize,
+    shape: Shape,
+    contentColor: Color,
+    containerColor: Color,
+    tonalElevation: Dp,
+    shadowElevation: Dp,
+    content: @Composable () -> Unit
+) {
+    val drawCaretModifier =
+        if (caretSize.isSpecified) {
+            val density = LocalDensity.current
+            val configuration = LocalConfiguration.current
+            Modifier.drawCaret { anchorLayoutCoordinates ->
+                drawCaretWithPath(
+                    CaretType.Plain,
+                    density,
+                    configuration,
+                    containerColor,
+                    caretSize,
+                    anchorLayoutCoordinates
+                )
+            }.then(modifier)
+        } else modifier
+    Surface(
+        modifier = drawCaretModifier,
+        shape = shape,
+        color = containerColor,
+        tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation
+    ) {
+        Box(modifier = Modifier
+            .sizeIn(
+                minWidth = TooltipMinWidth,
+                maxWidth = PlainTooltipMaxWidth,
+                minHeight = TooltipMinHeight
+            )
+            .padding(PlainTooltipContentPadding)
+        ) {
+            val textStyle =
+                PlainTooltipTokens.SupportingTextFont.value
+
+            CompositionLocalProvider(
+                LocalContentColor provides contentColor,
+                LocalTextStyle provides textStyle,
+                content = content
+            )
+        }
+    }
+}
+
+/**
+ * Rich text tooltip that allows the user to pass in a title, text, and action.
+ * Tooltips are used to provide a descriptive message.
+ *
+ * Usually used with [TooltipBox]
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param title An optional title for the tooltip.
+ * @param action An optional action for the tooltip.
+ * @param caretSize [DpSize] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Please see [TooltipDefaults.caretSize] to
+ * see the default dimensions. Pass in Dp.Unspecified for this parameter if no caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param text the composable that will be used to populate the rich tooltip's text.
+ */
+@Composable
+@ExperimentalMaterial3Api
+actual fun TooltipScope.RichTooltip(
+    modifier: Modifier,
+    title: (@Composable () -> Unit)?,
+    action: (@Composable () -> Unit)?,
+    caretSize: DpSize,
+    shape: Shape,
+    colors: RichTooltipColors,
+    tonalElevation: Dp,
+    shadowElevation: Dp,
+    text: @Composable () -> Unit
+) {
+    val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
+    val elevatedColor =
+        MaterialTheme.colorScheme.applyTonalElevation(
+            colors.containerColor,
+            absoluteElevation
+        )
+    val drawCaretModifier =
+        if (caretSize.isSpecified) {
+            val density = LocalDensity.current
+            val configuration = LocalConfiguration.current
+            Modifier.drawCaret { anchorLayoutCoordinates ->
+                drawCaretWithPath(
+                    CaretType.Rich,
+                    density,
+                    configuration,
+                    elevatedColor,
+                    caretSize,
+                    anchorLayoutCoordinates
+                )
+            }.then(modifier)
+        } else modifier
+    Surface(
+        modifier = drawCaretModifier
+            .sizeIn(
+                minWidth = TooltipMinWidth,
+                maxWidth = RichTooltipMaxWidth,
+                minHeight = TooltipMinHeight
+            ),
+        shape = shape,
+        color = colors.containerColor,
+        tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation
+    ) {
+        val actionLabelTextStyle = RichTooltipTokens.ActionLabelTextFont.value
+        val subheadTextStyle = RichTooltipTokens.SubheadFont.value
+        val supportingTextStyle = RichTooltipTokens.SupportingTextFont.value
+
+        Column(
+            modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
+        ) {
+            title?.let {
+                Box(
+                    modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides colors.titleContentColor,
+                        LocalTextStyle provides subheadTextStyle,
+                        content = it
+                    )
+                }
+            }
+            Box(
+                modifier = Modifier.textVerticalPadding(
+                    title != null,
+                    action != null
+                )
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides colors.contentColor,
+                    LocalTextStyle provides supportingTextStyle,
+                    content = text
+                )
+            }
+            action?.let {
+                Box(
+                    modifier = Modifier
+                        .requiredHeightIn(min = ActionLabelMinHeight)
+                        .padding(bottom = ActionLabelBottomPadding)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides colors.actionContentColor,
+                        LocalTextStyle provides actionLabelTextStyle,
+                        content = it
+                    )
+                }
+            }
+        }
+    }
+}
+
+@ExperimentalMaterial3Api
+private fun CacheDrawScope.drawCaretWithPath(
+    caretType: CaretType,
+    density: Density,
+    configuration: Configuration,
+    containerColor: Color,
+    caretSize: DpSize,
+    anchorLayoutCoordinates: LayoutCoordinates?
+): DrawResult {
+    val path = Path()
+
+    if (anchorLayoutCoordinates != null) {
+        val caretHeightPx: Int
+        val caretWidthPx: Int
+        val screenWidthPx: Int
+        val tooltipAnchorSpacing: Int
+        with(density) {
+            caretHeightPx = caretSize.height.roundToPx()
+            caretWidthPx = caretSize.width.roundToPx()
+            screenWidthPx = configuration.screenWidthDp.dp.roundToPx()
+            tooltipAnchorSpacing = SpacingBetweenTooltipAndAnchor.roundToPx()
+        }
+        val anchorBounds = anchorLayoutCoordinates.boundsInWindow()
+        val anchorLeft = anchorBounds.left
+        val anchorRight = anchorBounds.right
+        val anchorTop = anchorBounds.top
+        val anchorMid = (anchorRight + anchorLeft) / 2
+        val anchorWidth = anchorRight - anchorLeft
+        val tooltipWidth = this.size.width
+        val tooltipHeight = this.size.height
+        val isCaretTop = anchorTop - tooltipHeight - tooltipAnchorSpacing < 0
+        val caretY = if (isCaretTop) { 0f } else { tooltipHeight }
+
+        val position: Offset
+        if (caretType == CaretType.Plain) {
+            position =
+                if (anchorMid + tooltipWidth / 2 > screenWidthPx) {
+                    // Caret needs to be near the right
+                    val anchorMidFromRightScreenEdge =
+                        screenWidthPx - anchorMid
+                    val caretX = tooltipWidth - anchorMidFromRightScreenEdge
+                    Offset(caretX, caretY)
+                } else {
+                    // Caret needs to be near the left
+                    val tooltipLeft =
+                        anchorLeft - (this.size.width / 2 - anchorWidth / 2)
+                    val caretX = anchorMid - maxOf(tooltipLeft, 0f)
+                    Offset(caretX, caretY)
+                }
+        } else {
+            // Default the caret to the left
+            var preferredPosition = Offset(anchorMid - anchorLeft, caretY)
+            if (anchorLeft + tooltipWidth > screenWidthPx) {
+                // Need to move the caret to the right
+                preferredPosition = Offset(anchorMid - (anchorRight - tooltipWidth), caretY)
+                if (anchorRight - tooltipWidth < 0) {
+                    // Need to center the caret
+                    // Caret might need to be offset depending on where
+                    // the tooltip is placed relative to the anchor
+                    if (anchorLeft - tooltipWidth / 2 + anchorWidth / 2 <= 0) {
+                        preferredPosition = Offset(anchorMid, caretY)
+                    } else if (anchorRight + tooltipWidth / 2 - anchorWidth / 2 >= screenWidthPx) {
+                        val anchorMidFromRightScreenEdge =
+                            screenWidthPx - anchorMid
+                        val caretX = tooltipWidth - anchorMidFromRightScreenEdge
+                        preferredPosition = Offset(caretX, caretY)
+                    } else {
+                        preferredPosition = Offset(tooltipWidth / 2, caretY)
+                    }
+                }
+            }
+            position = preferredPosition
+        }
+
+        if (isCaretTop) {
+            path.apply {
+                moveTo(x = position.x, y = position.y)
+                lineTo(x = position.x + caretWidthPx / 2, y = position.y)
+                lineTo(x = position.x, y = position.y - caretHeightPx)
+                lineTo(x = position.x - caretWidthPx / 2, y = position.y)
+                close()
+            }
+        } else {
+            path.apply {
+                moveTo(x = position.x, y = position.y)
+                lineTo(x = position.x + caretWidthPx / 2, y = position.y)
+                lineTo(x = position.x, y = position.y + caretHeightPx.toFloat())
+                lineTo(x = position.x - caretWidthPx / 2, y = position.y)
+                close()
+            }
+        }
+    }
+
+    return onDrawWithContent {
+        if (anchorLayoutCoordinates != null) {
+            drawContent()
+            drawPath(
+                path = path,
+                color = containerColor
+            )
+        }
+    }
+}
+
+@ExperimentalMaterial3Api
+private enum class CaretType {
+    Plain, Rich
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index 18f443c..a673371 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -26,42 +26,35 @@
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.paddingFromBaseline
-import androidx.compose.foundation.layout.requiredHeightIn
-import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.material3.internal.BasicTooltipBox
 import androidx.compose.material3.internal.BasicTooltipDefaults
 import androidx.compose.material3.tokens.ElevationTokens
 import androidx.compose.material3.tokens.PlainTooltipTokens
 import androidx.compose.material3.tokens.RichTooltipTokens
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.draw.CacheDrawScope
 import androidx.compose.ui.draw.DrawResult
 import androidx.compose.ui.draw.drawWithCache
-import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.graphics.takeOrElse
 import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.boundsInWindow
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.IntOffset
@@ -69,7 +62,6 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.isSpecified
 import androidx.compose.ui.window.PopupPositionProvider
 import kotlinx.coroutines.CancellableContinuation
 import kotlinx.coroutines.suspendCancellableCoroutine
@@ -208,7 +200,7 @@
  */
 @Composable
 @ExperimentalMaterial3Api
-fun TooltipScope.PlainTooltip(
+expect fun TooltipScope.PlainTooltip(
     modifier: Modifier = Modifier,
     caretSize: DpSize = DpSize.Unspecified,
     shape: Shape = TooltipDefaults.plainTooltipContainerShape,
@@ -217,48 +209,7 @@
     tonalElevation: Dp = 0.dp,
     shadowElevation: Dp = 0.dp,
     content: @Composable () -> Unit
-) {
-    val drawCaretModifier =
-        if (caretSize.isSpecified) {
-            val density = LocalDensity.current
-            val windowContainerWidthInPx = windowContainerWidthInPx()
-            Modifier.drawCaret { anchorLayoutCoordinates ->
-                drawCaretWithPath(
-                    CaretType.Plain,
-                    density,
-                    windowContainerWidthInPx,
-                    containerColor,
-                    caretSize,
-                    anchorLayoutCoordinates
-                )
-            }.then(modifier)
-        } else modifier
-    Surface(
-        modifier = drawCaretModifier,
-        shape = shape,
-        color = containerColor,
-        tonalElevation = tonalElevation,
-        shadowElevation = shadowElevation
-    ) {
-        Box(modifier = Modifier
-            .sizeIn(
-                minWidth = TooltipMinWidth,
-                maxWidth = PlainTooltipMaxWidth,
-                minHeight = TooltipMinHeight
-            )
-            .padding(PlainTooltipContentPadding)
-        ) {
-            val textStyle =
-                PlainTooltipTokens.SupportingTextFont.value
-
-            CompositionLocalProvider(
-                LocalContentColor provides contentColor,
-                LocalTextStyle provides textStyle,
-                content = content
-            )
-        }
-    }
-}
+)
 
 /**
  * Rich text tooltip that allows the user to pass in a title, text, and action.
@@ -280,7 +231,7 @@
  */
 @Composable
 @ExperimentalMaterial3Api
-fun TooltipScope.RichTooltip(
+expect fun TooltipScope.RichTooltip(
     modifier: Modifier = Modifier,
     title: (@Composable () -> Unit)? = null,
     action: (@Composable () -> Unit)? = null,
@@ -290,86 +241,7 @@
     tonalElevation: Dp = ElevationTokens.Level0,
     shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
     text: @Composable () -> Unit
-) {
-    val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
-    val elevatedColor =
-        MaterialTheme.colorScheme.applyTonalElevation(
-            colors.containerColor,
-            absoluteElevation
-        )
-    val drawCaretModifier =
-        if (caretSize.isSpecified) {
-            val density = LocalDensity.current
-            val windowContainerWidthInPx = windowContainerWidthInPx()
-            Modifier.drawCaret { anchorLayoutCoordinates ->
-                drawCaretWithPath(
-                    CaretType.Rich,
-                    density,
-                    windowContainerWidthInPx,
-                    elevatedColor,
-                    caretSize,
-                    anchorLayoutCoordinates
-                )
-            }.then(modifier)
-        } else modifier
-    Surface(
-        modifier = drawCaretModifier
-            .sizeIn(
-                minWidth = TooltipMinWidth,
-                maxWidth = RichTooltipMaxWidth,
-                minHeight = TooltipMinHeight
-            ),
-        shape = shape,
-        color = colors.containerColor,
-        tonalElevation = tonalElevation,
-        shadowElevation = shadowElevation
-    ) {
-        val actionLabelTextStyle = RichTooltipTokens.ActionLabelTextFont.value
-        val subheadTextStyle = RichTooltipTokens.SubheadFont.value
-        val supportingTextStyle = RichTooltipTokens.SupportingTextFont.value
-
-        Column(
-            modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
-        ) {
-            title?.let {
-                Box(
-                    modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
-                ) {
-                    CompositionLocalProvider(
-                        LocalContentColor provides colors.titleContentColor,
-                        LocalTextStyle provides subheadTextStyle,
-                        content = it
-                    )
-                }
-            }
-            Box(
-                modifier = Modifier.textVerticalPadding(
-                    title != null,
-                    action != null
-                )
-            ) {
-                CompositionLocalProvider(
-                    LocalContentColor provides colors.contentColor,
-                    LocalTextStyle provides supportingTextStyle,
-                    content = text
-                )
-            }
-            action?.let {
-                Box(
-                    modifier = Modifier
-                        .requiredHeightIn(min = ActionLabelMinHeight)
-                        .padding(bottom = ActionLabelBottomPadding)
-                ) {
-                    CompositionLocalProvider(
-                        LocalContentColor provides colors.actionContentColor,
-                        LocalTextStyle provides actionLabelTextStyle,
-                        content = it
-                    )
-                }
-            }
-        }
-    }
-}
+)
 
 /**
  * Tooltip defaults that contain default values for both [PlainTooltip] and [RichTooltip]
@@ -638,7 +510,7 @@
     override val isVisible: Boolean
         get() = transition.currentState || transition.targetState
 
-    /**
+            /**
      * continuation used to clean up
      */
     private var job: (CancellableContinuation<Unit>)? = null
@@ -812,118 +684,6 @@
     )
 }
 
-@ExperimentalMaterial3Api
-private fun CacheDrawScope.drawCaretWithPath(
-    caretType: CaretType,
-    density: Density,
-    windowContainerWidthInPx: Int,
-    containerColor: Color,
-    caretSize: DpSize,
-    anchorLayoutCoordinates: LayoutCoordinates?
-): DrawResult {
-    val path = Path()
-
-    if (anchorLayoutCoordinates != null) {
-        val caretHeightPx: Int
-        val caretWidthPx: Int
-        val tooltipAnchorSpacing: Int
-        with(density) {
-            caretHeightPx = caretSize.height.roundToPx()
-            caretWidthPx = caretSize.width.roundToPx()
-            tooltipAnchorSpacing = SpacingBetweenTooltipAndAnchor.roundToPx()
-        }
-        val anchorBounds = anchorLayoutCoordinates.boundsInWindow()
-        val anchorLeft = anchorBounds.left
-        val anchorRight = anchorBounds.right
-        val anchorTop = anchorBounds.top
-        val anchorMid = (anchorRight + anchorLeft) / 2
-        val anchorWidth = anchorRight - anchorLeft
-        val tooltipWidth = this.size.width
-        val tooltipHeight = this.size.height
-        val isCaretTop = anchorTop - tooltipHeight - tooltipAnchorSpacing < 0
-        val caretY = if (isCaretTop) { 0f } else { tooltipHeight }
-
-        val position: Offset
-        if (caretType == CaretType.Plain) {
-            position =
-                if (anchorMid + tooltipWidth / 2 > windowContainerWidthInPx) {
-                    // Caret needs to be near the right
-                    val anchorMidFromRightScreenEdge =
-                        windowContainerWidthInPx - anchorMid
-                    val caretX = tooltipWidth - anchorMidFromRightScreenEdge
-                    Offset(caretX, caretY)
-                } else {
-                    // Caret needs to be near the left
-                    val tooltipLeft =
-                        anchorLeft - (this.size.width / 2 - anchorWidth / 2)
-                    val caretX = anchorMid - maxOf(tooltipLeft, 0f)
-                    Offset(caretX, caretY)
-                }
-        } else {
-            // Default the caret to the left
-            var preferredPosition = Offset(anchorMid - anchorLeft, caretY)
-            if (anchorLeft + tooltipWidth > windowContainerWidthInPx) {
-                // Need to move the caret to the right
-                preferredPosition = Offset(anchorMid - (anchorRight - tooltipWidth), caretY)
-                if (anchorRight - tooltipWidth < 0) {
-                    // Need to center the caret
-                    // Caret might need to be offset depending on where
-                    // the tooltip is placed relative to the anchor
-                    if (anchorLeft - tooltipWidth / 2 + anchorWidth / 2 <= 0) {
-                        preferredPosition = Offset(anchorMid, caretY)
-                    } else if (
-                        anchorRight + tooltipWidth / 2 - anchorWidth / 2 >= windowContainerWidthInPx
-                    ) {
-                        val anchorMidFromRightScreenEdge =
-                            windowContainerWidthInPx - anchorMid
-                        val caretX = tooltipWidth - anchorMidFromRightScreenEdge
-                        preferredPosition = Offset(caretX, caretY)
-                    } else {
-                        preferredPosition = Offset(tooltipWidth / 2, caretY)
-                    }
-                }
-            }
-            position = preferredPosition
-        }
-
-        if (isCaretTop) {
-            path.apply {
-                moveTo(x = position.x, y = position.y)
-                lineTo(x = position.x + caretWidthPx / 2, y = position.y)
-                lineTo(x = position.x, y = position.y - caretHeightPx)
-                lineTo(x = position.x - caretWidthPx / 2, y = position.y)
-                close()
-            }
-        } else {
-            path.apply {
-                moveTo(x = position.x, y = position.y)
-                lineTo(x = position.x + caretWidthPx / 2, y = position.y)
-                lineTo(x = position.x, y = position.y + caretHeightPx.toFloat())
-                lineTo(x = position.x - caretWidthPx / 2, y = position.y)
-                close()
-            }
-        }
-    }
-
-    return onDrawWithContent {
-        if (anchorLayoutCoordinates != null) {
-            drawContent()
-            drawPath(
-                path = path,
-                color = containerColor
-            )
-        }
-    }
-}
-
-@ExperimentalMaterial3Api
-private enum class CaretType {
-    Plain, Rich
-}
-
-@Composable
-internal expect fun windowContainerWidthInPx(): Int
-
 internal val SpacingBetweenTooltipAndAnchor = 4.dp
 internal val TooltipMinHeight = 24.dp
 internal val TooltipMinWidth = 40.dp
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt
index c3cbf05..71f431b 100644
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt
@@ -16,12 +16,158 @@
 
 package androidx.compose.material3
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.material3.tokens.PlainTooltipTokens
+import androidx.compose.material3.tokens.RichTooltipTokens
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.LocalComposeScene
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
 
+/**
+ * Plain tooltip that provides a descriptive message.
+ *
+ * Usually used with [TooltipBox].
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param caretSize [DpSize] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Please see [TooltipDefaults.caretSize] to
+ * see the default dimensions. Pass in Dp.Unspecified for this parameter if no caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param contentColor [Color] that will be applied to the tooltip's content.
+ * @param containerColor [Color] that will be applied to the tooltip's container.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param content the composable that will be used to populate the tooltip's content.
+ */
 @Composable
-internal actual fun windowContainerWidthInPx(): Int {
-    // TODO: Upstream a proper way to get this from JetBrains fork
-    //  LocalWindowInfo.current.containerSize.width
-    return LocalComposeScene.current.constraints.maxWidth
+@ExperimentalMaterial3Api
+actual fun TooltipScope.PlainTooltip(
+    modifier: Modifier,
+    caretSize: DpSize,
+    shape: Shape,
+    contentColor: Color,
+    containerColor: Color,
+    tonalElevation: Dp,
+    shadowElevation: Dp,
+    content: @Composable () -> Unit
+) {
+    Surface(
+        modifier = modifier,
+        shape = shape,
+        color = containerColor,
+        tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation
+    ) {
+        Box(modifier = Modifier
+            .sizeIn(
+                minWidth = TooltipMinWidth,
+                maxWidth = PlainTooltipMaxWidth,
+                minHeight = TooltipMinHeight
+            )
+            .padding(PlainTooltipContentPadding)
+        ) {
+            val textStyle = PlainTooltipTokens.SupportingTextFont.value
+            CompositionLocalProvider(
+                LocalContentColor provides contentColor,
+                LocalTextStyle provides textStyle,
+                content = content
+            )
+        }
+    }
+}
+
+/**
+ * Rich text tooltip that allows the user to pass in a title, text, and action.
+ * Tooltips are used to provide a descriptive message.
+ *
+ * Usually used with [TooltipBox]
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param title An optional title for the tooltip.
+ * @param action An optional action for the tooltip.
+ * @param caretSize [DpSize] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Please see [TooltipDefaults.caretSize] to
+ * see the default dimensions. Pass in Dp.Unspecified for this parameter if no caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param text the composable that will be used to populate the rich tooltip's text.
+ */
+@Composable
+@ExperimentalMaterial3Api
+actual fun TooltipScope.RichTooltip(
+    modifier: Modifier,
+    title: (@Composable () -> Unit)?,
+    action: (@Composable () -> Unit)?,
+    caretSize: DpSize,
+    shape: Shape,
+    colors: RichTooltipColors,
+    tonalElevation: Dp,
+    shadowElevation: Dp,
+    text: @Composable () -> Unit
+) {
+    Surface(
+        modifier = modifier
+            .sizeIn(
+                minWidth = TooltipMinWidth,
+                maxWidth = RichTooltipMaxWidth,
+                minHeight = TooltipMinHeight
+            ),
+        shape = shape,
+        color = colors.containerColor,
+        tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation
+    ) {
+        val actionLabelTextStyle = RichTooltipTokens.ActionLabelTextFont.value
+        val subheadTextStyle = RichTooltipTokens.SubheadFont.value
+        val supportingTextStyle = RichTooltipTokens.SupportingTextFont.value
+
+        Column(
+            modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
+        ) {
+            title?.let {
+                Box(
+                    modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides colors.titleContentColor,
+                        LocalTextStyle provides subheadTextStyle,
+                        content = it
+                    )
+                }
+            }
+            Box(
+                modifier = Modifier.textVerticalPadding(title != null, action != null)
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides colors.contentColor,
+                    LocalTextStyle provides supportingTextStyle,
+                    content = text
+                )
+            }
+            action?.let {
+                Box(
+                    modifier = Modifier
+                        .requiredHeightIn(min = ActionLabelMinHeight)
+                        .padding(bottom = ActionLabelBottomPadding)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides colors.actionContentColor,
+                        LocalTextStyle provides actionLabelTextStyle,
+                        content = it
+                    )
+                }
+            }
+        }
+    }
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
index ca6348c..ae3ba98 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
@@ -38,7 +38,6 @@
 import androidx.compose.ui.graphics.layer.drawLayer
 import androidx.compose.ui.graphics.layer.setOutline
 import androidx.compose.ui.graphics.nativeCanvas
-import androidx.compose.ui.internal.throwIllegalStateException
 import androidx.compose.ui.layout.GraphicLayerInfo
 import androidx.compose.ui.node.OwnedLayer
 import androidx.compose.ui.unit.Density
@@ -50,7 +49,7 @@
 import androidx.compose.ui.unit.toSize
 
 internal class GraphicsLayerOwnerLayer(
-    private val graphicsLayer: GraphicsLayer,
+    private var graphicsLayer: GraphicsLayer,
     // when we have a context it means the object is created by us and we need to release it
     private val context: GraphicsContext?,
     private val ownerView: AndroidComposeView,
@@ -303,7 +302,10 @@
         invalidateParentLayer = null
         isDestroyed = true
         isDirty = false
-        context?.releaseGraphicsLayer(graphicsLayer)
+        if (context != null) {
+            context.releaseGraphicsLayer(graphicsLayer)
+            ownerView.recycle(this)
+        }
     }
 
     override fun mapOffset(point: Offset, inverse: Boolean): Offset {
@@ -331,7 +333,25 @@
         drawBlock: (canvas: Canvas, parentLayer: GraphicsLayer?) -> Unit,
         invalidateParentLayer: () -> Unit
     ) {
-        throwIllegalStateException("reuseLayer is not supported yet")
+        val context = requireNotNull(context) {
+            "currently reuse is only supported when we manage the layer lifecycle"
+        }
+        require(graphicsLayer.isReleased) { "layer should have been released before reuse" }
+
+        // recreate a layer
+        graphicsLayer = context.createGraphicsLayer()
+        isDestroyed = false
+
+        // apply new params
+        this.drawBlock = drawBlock
+        this.invalidateParentLayer = invalidateParentLayer
+
+        // reset mutable variables to their initial values
+        transformOrigin = TransformOrigin.Center
+        drawnWithEnabledZ = false
+        size = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
+        outline = null
+        mutatedFields = 0
     }
 
     override fun transform(matrix: Matrix) {
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt
index d510fac..2f16c79 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt
@@ -56,9 +56,7 @@
 import kotlinx.coroutines.launch
 import org.jetbrains.skia.Canvas
 
-// TODO: This val should not be public!
-//  Upstream current state of [ComposeScene] from JetBrains fork
-val LocalComposeScene = staticCompositionLocalOf<ComposeScene> {
+internal val LocalComposeScene = staticCompositionLocalOf<ComposeScene> {
     error("CompositionLocal LocalComposeScene not provided")
 }
 
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index c0ebd31..d6775c43 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -153,11 +153,11 @@
     docs("androidx.customview:customview:1.2.0-alpha02")
     // TODO(b/294531403): Turn on apiSince for customview-poolingcontainer when it releases as alpha
     docsWithoutApiSince("androidx.customview:customview-poolingcontainer:1.0.0-rc01")
-    docs("androidx.datastore:datastore:1.1.1")
-    docs("androidx.datastore:datastore-core:1.1.1")
-    docs("androidx.datastore:datastore-core-okio:1.1.1")
-    docs("androidx.datastore:datastore-preferences:1.1.1")
-    docs("androidx.datastore:datastore-preferences-core:1.1.1")
+    kmpDocs("androidx.datastore:datastore:1.1.1")
+    kmpDocs("androidx.datastore:datastore-core:1.1.1")
+    kmpDocs("androidx.datastore:datastore-core-okio:1.1.1")
+    kmpDocs("androidx.datastore:datastore-preferences:1.1.1")
+    kmpDocs("androidx.datastore:datastore-preferences-core:1.1.1")
     docs("androidx.datastore:datastore-preferences-rxjava2:1.1.1")
     docs("androidx.datastore:datastore-preferences-rxjava3:1.1.1")
     docs("androidx.datastore:datastore-rxjava2:1.1.1")