Merge "Update typefonts for M3 Cards" into androidx-main
diff --git a/browser/browser/api/api_lint.ignore b/browser/browser/api/api_lint.ignore
index 68c85ec..bec6b20 100644
--- a/browser/browser/api/api_lint.ignore
+++ b/browser/browser/api/api_lint.ignore
@@ -157,6 +157,8 @@
     androidx.browser.customtabs.CustomTabsIntent does not declare a `getSecondaryToolbarViews()` method matching method androidx.browser.customtabs.CustomTabsIntent.Builder.setSecondaryToolbarViews(android.widget.RemoteViews,int[],android.app.PendingIntent)
 MissingGetterMatchingBuilder: androidx.browser.customtabs.CustomTabsIntent.Builder#setSession(androidx.browser.customtabs.CustomTabsSession):
     androidx.browser.customtabs.CustomTabsIntent does not declare a `getSession()` method matching method androidx.browser.customtabs.CustomTabsIntent.Builder.setSession(androidx.browser.customtabs.CustomTabsSession)
+MissingGetterMatchingBuilder: androidx.browser.customtabs.CustomTabsIntent.Builder#setShareIdentityEnabled(boolean):
+    androidx.browser.customtabs.CustomTabsIntent does not declare a `isShareIdentityEnabled()` method matching method androidx.browser.customtabs.CustomTabsIntent.Builder.setShareIdentityEnabled(boolean)
 MissingGetterMatchingBuilder: androidx.browser.customtabs.CustomTabsIntent.Builder#setShareState(int):
     androidx.browser.customtabs.CustomTabsIntent does not declare a `getShareState()` method matching method androidx.browser.customtabs.CustomTabsIntent.Builder.setShareState(int)
 MissingGetterMatchingBuilder: androidx.browser.customtabs.CustomTabsIntent.Builder#setShowTitle(boolean):
diff --git a/browser/browser/api/current.txt b/browser/browser/api/current.txt
index 4878f05..68c6b14 100644
--- a/browser/browser/api/current.txt
+++ b/browser/browser/api/current.txt
@@ -199,6 +199,7 @@
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[]?, android.app.PendingIntent?);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setSendToExternalDefaultHandlerEnabled(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setSession(androidx.browser.customtabs.CustomTabsSession);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setShareIdentityEnabled(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setShareState(int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowOnToolbarEnabled(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
diff --git a/browser/browser/api/restricted_current.txt b/browser/browser/api/restricted_current.txt
index 2eab941..e978f0e 100644
--- a/browser/browser/api/restricted_current.txt
+++ b/browser/browser/api/restricted_current.txt
@@ -210,6 +210,7 @@
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[]?, android.app.PendingIntent?);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setSendToExternalDefaultHandlerEnabled(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setSession(androidx.browser.customtabs.CustomTabsSession);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setShareIdentityEnabled(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setShareState(int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowOnToolbarEnabled(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
index d19d42f..53d1504 100644
--- a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
@@ -20,6 +20,7 @@
 import static androidx.annotation.Dimension.PX;
 
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -535,12 +536,13 @@
         private final CustomTabColorSchemeParams.Builder mDefaultColorSchemeBuilder =
                 new CustomTabColorSchemeParams.Builder();
         @Nullable private ArrayList mMenuItems;
-        @Nullable private Bundle mStartAnimationBundle;
+        @Nullable private ActivityOptions mActivityOptions;
         @Nullable private ArrayList mActionButtons;
         @Nullable private SparseArray mColorSchemeParamBundles;
         @Nullable private Bundle mDefaultColorSchemeBundle;
         @ShareState private int mShareState = SHARE_STATE_DEFAULT;
         private boolean mInstantAppsEnabled = true;
+        private boolean mShareIdentity;
 
         /**
          * Creates a {@link CustomTabsIntent.Builder} object associated with no
@@ -913,8 +915,13 @@
         @SuppressWarnings("NullAway") // TODO: b/141869399
         public Builder setStartAnimations(
                 @NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) {
-            mStartAnimationBundle = ActivityOptionsCompat.makeCustomAnimation(
-                    context, enterResId, exitResId).toBundle();
+            // We use ActivityOptions, not ActivityOptionsCompat, to build the start activity
+            // options, since we might set another option (share identity, which is not
+            // available yet via ActivityOptionsCompat) before turning it to a Bundle.
+            // TODO(b/296463161): Update androidx.core.core lib to support the new option via
+            // ActivityOptionsCompat and use it here instead of ActivityOptions.
+            mActivityOptions = ActivityOptions.makeCustomAnimation(
+                    context, enterResId, exitResId);
             return this;
         }
 
@@ -1161,6 +1168,16 @@
         }
 
         /**
+         * Allow Custom Tabs to obtain the caller's identity i.e. package name.
+         * @param enabled Whether the identity sharing is enabled.
+         */
+        @NonNull
+        public Builder setShareIdentityEnabled(boolean enabled) {
+            mShareIdentity = enabled;
+            return this;
+        }
+
+        /**
          * Combines all the options that have been set and returns a new {@link CustomTabsIntent}
          * object.
          */
@@ -1195,7 +1212,14 @@
                 setCurrentLocaleAsDefaultAcceptLanguage();
             }
 
-            return new CustomTabsIntent(mIntent, mStartAnimationBundle);
+            Bundle bundle = null;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                setShareIdentityEnabled();
+            }
+            if (mActivityOptions != null) {
+                bundle = mActivityOptions.toBundle();
+            }
+            return new CustomTabsIntent(mIntent, bundle);
         }
 
         /**
@@ -1214,6 +1238,14 @@
                 }
             }
         }
+
+        @RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        private void setShareIdentityEnabled() {
+            if (mActivityOptions == null) {
+                mActivityOptions = Api23Impl.makeBasicActivityOptions();
+            }
+            Api34Impl.setShareIdentityEnabled(mActivityOptions, mShareIdentity);
+        }
     }
 
     /**
@@ -1395,6 +1427,14 @@
         return intent.getBooleanExtra(EXTRA_SHOW_ON_TOOLBAR, false);
     }
 
+    @RequiresApi(api = Build.VERSION_CODES.M)
+    private static class Api23Impl {
+        @DoNotInline
+        static ActivityOptions makeBasicActivityOptions() {
+            return ActivityOptions.makeBasic();
+        }
+    }
+
     @RequiresApi(api = Build.VERSION_CODES.N)
     private static class Api24Impl {
         @DoNotInline
@@ -1404,4 +1444,12 @@
             return (defaultLocaleList.size() > 0) ? defaultLocaleList.get(0).toLanguageTag(): null;
         }
     }
+
+    @RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static class Api34Impl {
+        @DoNotInline
+        static void setShareIdentityEnabled(ActivityOptions activityOptions, boolean enabled) {
+            activityOptions.setShareIdentityEnabled(enabled);
+        }
+    }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt
index 06d84bf..bfb2ddb 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt
@@ -20,7 +20,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.camera.camera2.pipe.CameraDevices
 import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.integration.internal.CameraGraphCreator
+import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraInfo
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.core.CameraInfo
@@ -32,15 +32,26 @@
 
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 class CameraCoordinatorAdapter(
+    private var cameraPipe: CameraPipe?,
     cameraDevices: CameraDevices,
-    private val cameraGraphCreator: CameraGraphCreator
 ) : CameraCoordinator {
-    @VisibleForTesting val cameraInternalMap = mutableMapOf()
-    @VisibleForTesting var concurrentCameraIdsSet = mutableSetOf>()
-    @VisibleForTesting var concurrentCameraIdMap = mutableMapOf>()
-    @VisibleForTesting var activeConcurrentCameraInfosList = mutableListOf()
-    @VisibleForTesting var concurrentMode: Int = CAMERA_OPERATING_MODE_UNSPECIFIED
-    @VisibleForTesting var concurrentModeOn = false
+    @VisibleForTesting
+    val cameraInternalMap = mutableMapOf()
+
+    @VisibleForTesting
+    var concurrentCameraIdsSet = mutableSetOf>()
+
+    @VisibleForTesting
+    var concurrentCameraIdMap = mutableMapOf>()
+
+    @VisibleForTesting
+    var activeConcurrentCameraInfosList = mutableListOf()
+
+    @VisibleForTesting
+    var concurrentMode: Int = CAMERA_OPERATING_MODE_UNSPECIFIED
+
+    @VisibleForTesting
+    var concurrentModeOn = false
 
     init {
         concurrentCameraIdsSet = cameraDevices.awaitConcurrentCameraIds()!!.toMutableSet()
@@ -85,8 +96,16 @@
 
     override fun setActiveConcurrentCameraInfos(cameraInfos: MutableList) {
         activeConcurrentCameraInfosList = cameraInfos
-        for (cameraInternalAdapter in cameraInternalMap.values) {
-            cameraInternalAdapter.resumeRefresh()
+        val graphConfigs = cameraInternalMap.values.map {
+            checkNotNull(it.getDeferredCameraGraphConfig()) {
+                "Every CameraInternal instance is expected to have a deferred CameraGraph config " +
+                    "when the active concurrent CameraInfos are set!"
+            }
+        }
+        val cameraGraphs = checkNotNull(cameraPipe).createCameraGraphs(graphConfigs)
+        check(cameraGraphs.size == cameraInternalMap.size)
+        for ((cameraInternalAdapter, cameraGraph) in cameraInternalMap.values.zip(cameraGraphs)) {
+            cameraInternalAdapter.resumeDeferredCameraGraphCreation(cameraGraph)
         }
     }
 
@@ -114,12 +133,11 @@
     override fun setCameraOperatingMode(@CameraOperatingMode cameraOperatingMode: Int) {
         concurrentMode = cameraOperatingMode
         concurrentModeOn = cameraOperatingMode == CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT
-        cameraGraphCreator.setConcurrentModeOn(concurrentModeOn)
         for (cameraInternalAdapter in cameraInternalMap.values) {
             if (cameraOperatingMode == CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT) {
-                cameraInternalAdapter.pauseRefresh()
+                cameraInternalAdapter.setCameraGraphCreationMode(createImmediately = false)
             } else if (cameraOperatingMode == CameraCoordinator.CAMERA_OPERATING_MODE_SINGLE) {
-                cameraInternalAdapter.resumeRefresh()
+                cameraInternalAdapter.setCameraGraphCreationMode(createImmediately = true)
             }
         }
     }
@@ -131,6 +149,7 @@
     }
 
     override fun shutdown() {
+        cameraPipe = null
         cameraInternalMap.clear()
         concurrentCameraIdsSet.clear()
         concurrentCameraIdMap.clear()
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
index 1c3dc2c..a22309f 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
@@ -72,7 +72,9 @@
     private var mAvailableCamerasSelector: CameraSelector? = availableCamerasSelector
     private var mAvailableCameraIds: List
     private val cameraCoordinator: CameraCoordinatorAdapter = CameraCoordinatorAdapter(
-        appComponent.getCameraDevices(), appComponent.getCameraGraphCreator())
+        appComponent.getCameraPipe(),
+        appComponent.getCameraDevices(),
+    )
 
     init {
         debug { "Created CameraFactoryAdapter" }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
index 5b39179..5020ead 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
@@ -19,6 +19,7 @@
 package androidx.camera.camera2.pipe.integration.adapter
 
 import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.core.Log.debug
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
@@ -60,11 +61,15 @@
         // TODO: Consider preloading the list of camera ids and metadata.
     }
 
-    fun pauseRefresh() = threads.scope.launch(threads.backgroundDispatcher) {
-        useCaseManager.pauseRefresh()
+    internal fun setCameraGraphCreationMode(createImmediately: Boolean) {
+        useCaseManager.setCameraGraphCreationMode(createImmediately)
     }
-    fun resumeRefresh() = threads.scope.launch(threads.backgroundDispatcher) {
-        useCaseManager.resumeRefresh()
+
+    internal fun getDeferredCameraGraphConfig(): CameraGraph.Config? =
+        useCaseManager.getDeferredCameraGraphConfig()
+
+    internal fun resumeDeferredCameraGraphCreation(cameraGraph: CameraGraph) {
+        useCaseManager.resumeDeferredComponentCreation(cameraGraph)
     }
 
     // Load / unload methods
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt
index 31d67ce..2ba8e67 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt
@@ -23,7 +23,6 @@
 import androidx.camera.camera2.pipe.CameraDevices
 import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.integration.impl.CameraInteropStateCallbackRepository
-import androidx.camera.camera2.pipe.integration.internal.CameraGraphCreator
 import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.CameraThreadConfig
 import dagger.Component
@@ -79,8 +78,6 @@
     fun getCameraPipe(): CameraPipe
     fun getCameraDevices(): CameraDevices
 
-    fun getCameraGraphCreator(): CameraGraphCreator
-
     @Component.Builder
     interface Builder {
         fun config(config: CameraAppConfig): Builder
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 aa17277..84bd53e 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
@@ -41,7 +41,6 @@
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraComponent
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
-import androidx.camera.camera2.pipe.integration.internal.CameraGraphCreator
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.core.UseCase
@@ -54,7 +53,6 @@
 import javax.inject.Provider
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.joinAll
-import kotlinx.coroutines.runBlocking
 
 /**
  * This class keeps track of the currently attached and active [UseCase]'s for a specific camera.
@@ -87,7 +85,6 @@
 @CameraScope
 class UseCaseManager @Inject constructor(
     private val cameraPipe: CameraPipe,
-    private val cameraGraphCreator: CameraGraphCreator,
     private val callbackMap: CameraCallbackMap,
     private val requestListener: ComboRequestListener,
     private val cameraConfig: CameraConfig,
@@ -115,7 +112,10 @@
     private var activeResumeEnabled = false
 
     @GuardedBy("lock")
-    private var refreshAttached = true
+    private var shouldCreateCameraGraphImmediately = true
+
+    @GuardedBy("lock")
+    private var deferredUseCaseManagerConfig: UseCaseManagerConfig? = null
 
     private val meteringRepeating by lazy {
         MeteringRepeating.Builder(
@@ -141,13 +141,17 @@
 
     private val allControls = controls.toMutableSet().apply { add(camera2CameraControl) }
 
-    fun pauseRefresh() = synchronized(lock) {
-        refreshAttached = false
+    internal fun setCameraGraphCreationMode(createImmediately: Boolean) = synchronized(lock) {
+        shouldCreateCameraGraphImmediately = createImmediately
+        if (shouldCreateCameraGraphImmediately) {
+            // Clear the UseCaseManager configuration that haven't been "resumed" when we return
+            // to single camera operating mode early.
+            deferredUseCaseManagerConfig = null
+        }
     }
 
-    fun resumeRefresh() = synchronized(lock) {
-        refreshAttached = true
-        refreshAttachedUseCases(attachedUseCases)
+    internal fun getDeferredCameraGraphConfig() = synchronized(lock) {
+        deferredUseCaseManagerConfig?.cameraGraphConfig
     }
 
     /**
@@ -283,9 +287,6 @@
 
     @GuardedBy("lock")
     private fun refreshAttachedUseCases(newUseCases: Set) {
-        if (!refreshAttached) {
-            return
-        }
         val useCases = newUseCases.toList()
 
         // Close prior camera graph
@@ -315,28 +316,53 @@
 
         val graphConfig = createCameraGraphConfig(
             sessionConfigAdapter, streamConfigMap, callbackMap,
-            requestListener, cameraConfig, cameraQuirks, cameraGraphFlags)
-        val cameraGraph =
-            runBlocking { cameraGraphCreator.createCameraGraph(cameraPipe, graphConfig) }
+            requestListener, cameraConfig, cameraQuirks, cameraGraphFlags
+        )
 
-        // Create and configure the new camera component.
-        _activeComponent =
-            builder.config(
-                UseCaseCameraConfig(
-                    useCases,
-                    sessionConfigAdapter,
-                    cameraStateAdapter,
-                    cameraGraph,
-                    streamConfigMap
-                )
-            )
-                .build()
-        for (control in allControls) {
-            control.useCaseCamera = camera
+        val useCaseManagerConfig = UseCaseManagerConfig(
+            useCases,
+            sessionConfigAdapter,
+            graphConfig,
+            streamConfigMap
+        )
+        if (!shouldCreateCameraGraphImmediately) {
+            deferredUseCaseManagerConfig = useCaseManagerConfig
+            return
         }
-        camera?.setActiveResumeMode(activeResumeEnabled)
+        val cameraGraph = cameraPipe.create(useCaseManagerConfig.cameraGraphConfig)
+        beginComponentCreation(useCaseManagerConfig, cameraGraph)
+    }
 
-        refreshRunningUseCases()
+    internal fun resumeDeferredComponentCreation(cameraGraph: CameraGraph) {
+        val config = synchronized(lock) { deferredUseCaseManagerConfig }
+        checkNotNull(config)
+        beginComponentCreation(config, cameraGraph)
+    }
+
+    private fun beginComponentCreation(
+        useCaseManagerConfig: UseCaseManagerConfig,
+        cameraGraph: CameraGraph
+    ) {
+        with(useCaseManagerConfig) {
+            // Create and configure the new camera component.
+            _activeComponent =
+                builder.config(
+                    UseCaseCameraConfig(
+                        useCases,
+                        sessionConfigAdapter,
+                        cameraStateAdapter,
+                        cameraGraph,
+                        streamConfigMap
+                    )
+                )
+                    .build()
+            for (control in allControls) {
+                control.useCaseCamera = camera
+            }
+            camera?.setActiveResumeMode(activeResumeEnabled)
+
+            refreshRunningUseCases()
+        }
     }
 
     @GuardedBy("lock")
@@ -464,6 +490,13 @@
     }
 
     companion object {
+        internal data class UseCaseManagerConfig(
+            val useCases: List,
+            val sessionConfigAdapter: SessionConfigAdapter,
+            val cameraGraphConfig: CameraGraph.Config,
+            val streamConfigMap: MutableMap
+        )
+
         fun SessionConfig.toCamera2ImplConfig(): Camera2ImplConfig {
             return Camera2ImplConfig(implementationOptions)
         }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraGraphCreator.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraGraphCreator.kt
deleted file mode 100644
index f0aabe8..0000000
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraGraphCreator.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.camera2.pipe.integration.internal
-
-import androidx.annotation.GuardedBy
-import androidx.annotation.RequiresApi
-import androidx.camera.camera2.pipe.CameraGraph
-import androidx.camera.camera2.pipe.CameraPipe
-import javax.inject.Inject
-import javax.inject.Singleton
-import kotlinx.coroutines.CompletableDeferred
-
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-@Singleton
-class CameraGraphCreator @Inject constructor() {
-    private val lock = Any()
-
-    @GuardedBy("lock")
-    var currentExpectedConfigs = 1
-
-    @GuardedBy("lock")
-    val currentConfigs = mutableListOf()
-
-    private var pendingDeferred: CompletableDeferred? = null
-
-    fun setConcurrentModeOn(on: Boolean) = synchronized(lock) {
-        currentExpectedConfigs = if (on) {
-            2
-        } else {
-            1
-        }
-    }
-
-    suspend fun createCameraGraph(cameraPipe: CameraPipe, config: CameraGraph.Config): CameraGraph {
-        var deferred: CompletableDeferred? = null
-        synchronized(lock) {
-            currentConfigs.add(config)
-            if (currentConfigs.size != currentExpectedConfigs) {
-                deferred = CompletableDeferred()
-                pendingDeferred = deferred
-            }
-        }
-        if (deferred != null) {
-            return deferred!!.await()
-        }
-        synchronized(lock) {
-            if (currentExpectedConfigs == 1) {
-                val cameraGraph = cameraPipe.create(config)
-                currentConfigs.clear()
-                return cameraGraph
-            } else {
-                val cameraGraphs = cameraPipe.createCameraGraphs(currentConfigs)
-                pendingDeferred?.complete(cameraGraphs.first())
-                currentConfigs.clear()
-                return cameraGraphs[1]
-            }
-        }
-    }
-}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt
index 4e9619b..442f7b1 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt
@@ -16,25 +16,31 @@
 
 package androidx.camera.camera2.pipe.integration.adapter
 
+import android.content.Context
 import android.os.Build
 import androidx.camera.camera2.pipe.CameraBackendId
+import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.integration.internal.CameraGraphCreator
+import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraInfoAdapterCreator
+import androidx.camera.camera2.pipe.testing.FakeCameraBackend
 import androidx.camera.camera2.pipe.testing.FakeCameraDevices
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT
 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_SINGLE
 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
 import androidx.camera.core.impl.CameraInfoInternal
+import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.reset
 import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
 
@@ -43,7 +49,9 @@
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class CameraCoordinatorAdapterTest {
 
-    private val cameraMetadata = FakeCameraMetadata()
+    private val cameraMetadata0 = FakeCameraMetadata(cameraId = CameraId("0"))
+    private val cameraMetadata1 = FakeCameraMetadata(cameraId = CameraId("1"))
+    private val cameraMetadata2 = FakeCameraMetadata(cameraId = CameraId("2"))
 
     private val cameraDevices = FakeCameraDevices(
         defaultCameraBackendId = CameraBackendId("0"),
@@ -51,16 +59,46 @@
             setOf(CameraBackendId("0"), CameraBackendId("1")),
             setOf(CameraBackendId("0"), CameraBackendId("2"))
         ),
-        cameraMetadataMap = mapOf(CameraBackendId("0") to listOf(cameraMetadata))
+        cameraMetadataMap = mapOf(
+            CameraBackendId("0") to listOf(
+                cameraMetadata0,
+                cameraMetadata1,
+                cameraMetadata2
+            )
+        )
     )
 
-    private val mockCameraGraphCreator: CameraGraphCreator = mock()
     private val mockCameraInternalAdapter0: CameraInternalAdapter = mock()
     private val mockCameraInternalAdapter1: CameraInternalAdapter = mock()
     private val mockCameraInternalAdapter2: CameraInternalAdapter = mock()
 
-    private val cameraCoordinatorAdapter = CameraCoordinatorAdapter(
-        cameraDevices, mockCameraGraphCreator)
+    private val mockCameraGraphConfig0 = CameraGraph.Config(
+        camera = CameraId("0"), streams = emptyList()
+    )
+    private val mockCameraGraphConfig1 = CameraGraph.Config(
+        camera = CameraId("1"), streams = emptyList()
+    )
+    private val mockCameraGraphConfig2 = CameraGraph.Config(
+        camera = CameraId("2"), streams = emptyList()
+    )
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val fakeCameraBackend = FakeCameraBackend(
+        fakeCameras = mapOf(
+            cameraMetadata0.camera to cameraMetadata0,
+            cameraMetadata1.camera to cameraMetadata1,
+            cameraMetadata2.camera to cameraMetadata2
+        )
+    )
+    private val cameraPipe = CameraPipe(
+        CameraPipe.Config(
+            context,
+            cameraBackendConfig = CameraPipe.CameraBackendConfig(
+                internalBackend = fakeCameraBackend
+            ),
+        )
+    )
+    private val cameraCoordinatorAdapter = CameraCoordinatorAdapter(cameraPipe, cameraDevices)
 
     @Before
     fun setUp() {
@@ -79,9 +117,17 @@
 
     @Test
     fun setAndGetActiveConcurrentCameraInfos() {
+        whenever(mockCameraInternalAdapter0.getDeferredCameraGraphConfig())
+            .thenReturn(mockCameraGraphConfig0)
+        whenever(mockCameraInternalAdapter1.getDeferredCameraGraphConfig())
+            .thenReturn(mockCameraGraphConfig1)
+        whenever(mockCameraInternalAdapter2.getDeferredCameraGraphConfig())
+            .thenReturn(mockCameraGraphConfig2)
+
         cameraCoordinatorAdapter.activeConcurrentCameraInfos = mutableListOf(
             FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("0")),
-            FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("1")))
+            FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("1"))
+        )
 
         assertThat(cameraCoordinatorAdapter.activeConcurrentCameraInfos.size).isEqualTo(2)
         val cameraInfo0 = cameraCoordinatorAdapter.activeConcurrentCameraInfos[0]
@@ -90,17 +136,25 @@
         val cameraInfo1 = cameraCoordinatorAdapter.activeConcurrentCameraInfos[1]
             as CameraInfoInternal
         assertThat(cameraInfo1.cameraId).isEqualTo("1")
-        verify(mockCameraInternalAdapter0).resumeRefresh()
-        verify(mockCameraInternalAdapter1).resumeRefresh()
+        verify(mockCameraInternalAdapter0).resumeDeferredCameraGraphCreation(any())
+        verify(mockCameraInternalAdapter1).resumeDeferredCameraGraphCreation(any())
     }
 
     @Test
     fun getPairedConcurrentCameraId() {
+        whenever(mockCameraInternalAdapter0.getDeferredCameraGraphConfig())
+            .thenReturn(mockCameraGraphConfig0)
+        whenever(mockCameraInternalAdapter1.getDeferredCameraGraphConfig())
+            .thenReturn(mockCameraGraphConfig1)
+        whenever(mockCameraInternalAdapter2.getDeferredCameraGraphConfig())
+            .thenReturn(mockCameraGraphConfig2)
+
         assertThat(cameraCoordinatorAdapter.getPairedConcurrentCameraId("0")).isNull()
 
         cameraCoordinatorAdapter.activeConcurrentCameraInfos = mutableListOf(
             FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("0")),
-            FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("1")))
+            FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("1"))
+        )
 
         assertThat(cameraCoordinatorAdapter.getPairedConcurrentCameraId("0")).isEqualTo("1")
     }
@@ -109,11 +163,10 @@
     fun setAndGetCameraOperatingMode() {
         cameraCoordinatorAdapter.cameraOperatingMode = CAMERA_OPERATING_MODE_CONCURRENT
 
-        verify(mockCameraInternalAdapter0).pauseRefresh()
-        verify(mockCameraInternalAdapter0, never()).resumeRefresh()
-        verify(mockCameraInternalAdapter1).pauseRefresh()
-        verify(mockCameraInternalAdapter1, never()).resumeRefresh()
-        verify(mockCameraGraphCreator).setConcurrentModeOn(true)
+        verify(mockCameraInternalAdapter0).setCameraGraphCreationMode(createImmediately = false)
+        verify(mockCameraInternalAdapter0, never()).resumeDeferredCameraGraphCreation(any())
+        verify(mockCameraInternalAdapter1).setCameraGraphCreationMode(createImmediately = false)
+        verify(mockCameraInternalAdapter1, never()).resumeDeferredCameraGraphCreation(any())
         assertThat(cameraCoordinatorAdapter.cameraOperatingMode)
             .isEqualTo(CAMERA_OPERATING_MODE_CONCURRENT)
 
@@ -121,25 +174,32 @@
         reset(mockCameraInternalAdapter1)
         cameraCoordinatorAdapter.cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
 
-        verify(mockCameraInternalAdapter0).resumeRefresh()
-        verify(mockCameraInternalAdapter1).resumeRefresh()
-        verify(mockCameraGraphCreator).setConcurrentModeOn(false)
+        verify(mockCameraInternalAdapter0).setCameraGraphCreationMode(createImmediately = true)
+        verify(mockCameraInternalAdapter1).setCameraGraphCreationMode(createImmediately = true)
         assertThat(cameraCoordinatorAdapter.cameraOperatingMode)
             .isEqualTo(CAMERA_OPERATING_MODE_SINGLE)
 
         reset(mockCameraInternalAdapter0)
         reset(mockCameraInternalAdapter1)
         cameraCoordinatorAdapter.cameraOperatingMode = CAMERA_OPERATING_MODE_UNSPECIFIED
-        verify(mockCameraInternalAdapter0, never()).resumeRefresh()
-        verify(mockCameraInternalAdapter1, never()).resumeRefresh()
+        verify(mockCameraInternalAdapter0, never()).resumeDeferredCameraGraphCreation(any())
+        verify(mockCameraInternalAdapter1, never()).resumeDeferredCameraGraphCreation(any())
     }
 
     @Test
     fun shutdown() {
+        whenever(mockCameraInternalAdapter0.getDeferredCameraGraphConfig())
+            .thenReturn(mockCameraGraphConfig0)
+        whenever(mockCameraInternalAdapter1.getDeferredCameraGraphConfig())
+            .thenReturn(mockCameraGraphConfig1)
+        whenever(mockCameraInternalAdapter2.getDeferredCameraGraphConfig())
+            .thenReturn(mockCameraGraphConfig2)
+
         cameraCoordinatorAdapter.cameraOperatingMode = CAMERA_OPERATING_MODE_CONCURRENT
         cameraCoordinatorAdapter.activeConcurrentCameraInfos = mutableListOf(
             FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("0")),
-            FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("1")))
+            FakeCameraInfoAdapterCreator.createCameraInfoAdapter(cameraId = CameraId("1"))
+        )
 
         cameraCoordinatorAdapter.shutdown()
 
@@ -148,7 +208,8 @@
         assertThat(cameraCoordinatorAdapter.concurrentCameraIdMap).isEmpty()
         assertThat(cameraCoordinatorAdapter.concurrentCameraIdsSet).isEmpty()
         assertThat(cameraCoordinatorAdapter.cameraOperatingMode).isEqualTo(
-            CAMERA_OPERATING_MODE_UNSPECIFIED)
+            CAMERA_OPERATING_MODE_UNSPECIFIED
+        )
         assertThat(cameraCoordinatorAdapter.concurrentModeOn).isFalse()
     }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
index f06d683..9a1df6c35 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
@@ -30,7 +30,6 @@
 import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera.RunningUseCasesChangeListener
-import androidx.camera.camera2.pipe.integration.internal.CameraGraphCreator
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.camera2.pipe.integration.testing.FakeCamera2CameraControlCompat
@@ -345,7 +344,6 @@
         val fakeCamera = FakeCamera()
         return UseCaseManager(
             cameraPipe = CameraPipe(CameraPipe.Config(ApplicationProvider.getApplicationContext())),
-            cameraGraphCreator = CameraGraphCreator(),
             cameraConfig = CameraConfig(cameraId),
             callbackMap = CameraCallbackMap(),
             requestListener = ComboRequestListener(),
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraGraphCreatorTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraGraphCreatorTest.kt
deleted file mode 100644
index 02100cc..0000000
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/CameraGraphCreatorTest.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.camera2.pipe.integration.internal
-
-import android.content.Context
-import android.graphics.Rect
-import android.hardware.camera2.CameraCharacteristics
-import android.hardware.camera2.CameraMetadata
-import android.os.Build
-import android.util.Size
-import androidx.camera.camera2.pipe.CameraGraph
-import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.CameraPipe
-import androidx.camera.camera2.pipe.CameraStream
-import androidx.camera.camera2.pipe.StreamFormat
-import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
-import androidx.test.core.app.ApplicationProvider
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.async
-import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.annotation.Config
-import org.robolectric.annotation.internal.DoNotInstrument
-import org.robolectric.shadow.api.Shadow
-import org.robolectric.shadows.ShadowCameraCharacteristics
-import org.robolectric.shadows.ShadowCameraManager
-import org.robolectric.shadows.StreamConfigurationMapBuilder
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(RobolectricCameraPipeTestRunner::class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-class CameraGraphCreatorTest {
-
-    private val cameraGraphCreator: CameraGraphCreator = CameraGraphCreator()
-    private val context = ApplicationProvider.getApplicationContext() as Context
-    private val cameraPipe = CameraPipe(CameraPipe.Config(context))
-
-    private val stream1Config = CameraStream.Config.create(
-        Size(640, 480), StreamFormat.YUV_420_888)
-    private val stream2Config = CameraStream.Config.create(
-        Size(1280, 720), StreamFormat.YUV_420_888)
-    private val cameraGraph1Config = CameraGraph.Config(CameraId("0"), listOf(stream1Config))
-    private val cameraGraph2Config = CameraGraph.Config(CameraId("1"), listOf(stream2Config))
-
-    @Before
-    fun setUp() {
-        setupCameras()
-    }
-
-    @Test
-    fun createCameraGraph_singleMode() = runTest {
-        cameraGraphCreator.setConcurrentModeOn(false)
-        val cameraGraph = cameraGraphCreator.createCameraGraph(cameraPipe, cameraGraph1Config)
-
-        advanceUntilIdle()
-        assertThat(cameraGraph).isNotNull()
-    }
-
-    @Test
-    fun createCameraGraph_concurrentMode() = runTest {
-        cameraGraphCreator.setConcurrentModeOn(true)
-
-        val cameraGraph0 = async {
-            cameraGraphCreator.createCameraGraph(cameraPipe, cameraGraph1Config)
-        }
-        advanceUntilIdle()
-        assertThat(cameraGraph0.isCompleted).isFalse()
-
-        val cameraGraph1 = async {
-            cameraGraphCreator.createCameraGraph(cameraPipe, cameraGraph2Config)
-        }
-        advanceUntilIdle()
-
-        assertThat(cameraGraph0.isCompleted).isTrue()
-        assertThat(cameraGraph1.isCompleted).isTrue()
-        assertThat(cameraGraph0.await()).isNotNull()
-        assertThat(cameraGraph1.await()).isNotNull()
-    }
-
-    private fun setupCameras() {
-        val capabilities =
-            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE)
-
-        initCharacteristics("0", CameraCharacteristics.LENS_FACING_BACK, capabilities)
-        initCharacteristics("1", CameraCharacteristics.LENS_FACING_FRONT, capabilities)
-    }
-
-    private fun initCharacteristics(cameraId: String, lensFacing: Int, capabilities: IntArray?) {
-        val sensorWidth = 640
-        val sensorHeight = 480
-
-        val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
-        val shadowCharacteristics =
-            Shadow.extract(characteristics).apply {
-
-                set(CameraCharacteristics.LENS_FACING, lensFacing)
-
-                set(
-                    CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE,
-                    Rect(0, 0, sensorWidth, sensorHeight)
-                )
-
-                set(
-                    CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL,
-                    CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
-                )
-
-                set(
-                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP,
-                    StreamConfigurationMapBuilder.newBuilder().build()
-                )
-            }
-
-        capabilities?.let {
-            shadowCharacteristics.set(
-                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES, capabilities
-            )
-        }
-
-        // Add the camera to the camera service
-        (Shadow.extract(
-            ApplicationProvider.getApplicationContext()
-                .getSystemService(Context.CAMERA_SERVICE)
-        ) as ShadowCameraManager).addCamera(cameraId, characteristics)
-    }
-}
diff --git a/camera/camera-effects/src/androidTest/java/androidx/camera/effects/opengl/GlContextDeviceTest.kt b/camera/camera-effects/src/androidTest/java/androidx/camera/effects/opengl/GlContextDeviceTest.kt
index d6b064d..2ea329a 100644
--- a/camera/camera-effects/src/androidTest/java/androidx/camera/effects/opengl/GlContextDeviceTest.kt
+++ b/camera/camera-effects/src/androidTest/java/androidx/camera/effects/opengl/GlContextDeviceTest.kt
@@ -74,4 +74,9 @@
         glContext.registerSurface(surface)
         glContext.drawAndSwap(surface, TIMESTAMP_NS)
     }
+
+    @Test
+    fun registerSurfaceWithoutDrawingOrReleasing_noException() {
+        glContext.registerSurface(surface)
+    }
 }
diff --git a/camera/camera-effects/src/androidTest/java/androidx/camera/effects/opengl/GlRendererDeviceTest.kt b/camera/camera-effects/src/androidTest/java/androidx/camera/effects/opengl/GlRendererDeviceTest.kt
new file mode 100644
index 0000000..795082f
--- /dev/null
+++ b/camera/camera-effects/src/androidTest/java/androidx/camera/effects/opengl/GlRendererDeviceTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.effects.opengl
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumentation tests for [GlRenderer].
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 21)
+class GlRendererDeviceTest {
+
+    private val glRenderer = GlRenderer()
+
+    @Before
+    fun setUp() {
+        glRenderer.init()
+    }
+
+    @After
+    fun tearDown() {
+        glRenderer.release()
+    }
+
+    // TODO(b/295407763): verify the input/output of the OpenGL renderer
+    @Test
+    fun placeholder() {
+        assertThat(true).isTrue()
+    }
+}
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlContext.java b/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlContext.java
index ee796b3e..56919d0 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlContext.java
+++ b/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlContext.java
@@ -71,15 +71,13 @@
     void init() {
         checkState(Objects.equals(mEglDisplay, EGL14.EGL_NO_DISPLAY), "Already initialized");
 
-        // TODO(b/295407763): make sure EGLDisplay, EGLConfig, and EGLContext are released when
-        //  there is exception.
         // Create EGLDisplay.
-        EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
-        if (Objects.equals(eglDisplay, EGL14.EGL_NO_DISPLAY)) {
+        mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+        if (Objects.equals(mEglDisplay, EGL14.EGL_NO_DISPLAY)) {
             throw new IllegalStateException("Unable to get EGL14 display");
         }
         int[] version = new int[2];
-        if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
+        if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) {
             throw new IllegalStateException("Unable to initialize EGL14");
         }
 
@@ -103,35 +101,28 @@
         EGLConfig[] configs = new EGLConfig[1];
         int[] numConfigs = new int[1];
         if (!EGL14.eglChooseConfig(
-                eglDisplay, attribToChooseConfig, 0, configs, 0, configs.length,
-                numConfigs, 0
+                mEglDisplay, attribToChooseConfig, 0, configs, 0, configs.length, numConfigs, 0
         )) {
             throw new IllegalStateException("Unable to find a suitable EGLConfig");
         }
-        EGLConfig eglConfig = configs[0];
+        mEglConfig = configs[0];
         int[] attribToCreateContext = {
                 EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
                 EGL14.EGL_NONE
         };
 
         // Create EGLContext.
-        EGLContext eglContext = EGL14.eglCreateContext(
-                eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT,
+        mEglContext = EGL14.eglCreateContext(
+                mEglDisplay, mEglConfig, EGL14.EGL_NO_CONTEXT,
                 attribToCreateContext, 0
         );
         checkEglErrorOrThrow("eglCreateContext");
         int[] values = new int[1];
         EGL14.eglQueryContext(
-                eglDisplay, eglContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values,
-                0
+                mEglDisplay, mEglContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values, 0
         );
         Logger.d(TAG, "EGLContext created, client version " + values[0]);
 
-        // All successful. Track the created objects.
-        mEglDisplay = eglDisplay;
-        mEglConfig = eglConfig;
-        mEglContext = eglContext;
-
         // Create a temporary surface to make it current.
         mTempSurface = create1x1PBufferSurface();
         makeCurrent(mTempSurface);
@@ -207,18 +198,19 @@
         }
     }
 
-    boolean release() {
-        if (!isInitialized()) {
-            return false;
+    void release() {
+        if (!Objects.equals(mEglDisplay, EGL14.EGL_NO_DISPLAY)) {
+            EGL14.eglMakeCurrent(
+                    mEglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
+                    EGL14.EGL_NO_CONTEXT
+            );
         }
-        EGL14.eglMakeCurrent(
-                mEglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
-                EGL14.EGL_NO_CONTEXT
-        );
 
         // Destroy EGLSurfaces
         for (EglSurface eglSurface : mRegisteredSurfaces.values()) {
-            destroyEglSurface(eglSurface);
+            if (eglSurface != null) {
+                destroyEglSurface(eglSurface);
+            }
         }
         mRegisteredSurfaces.clear();
 
@@ -230,15 +222,17 @@
         mCurrentSurface = null;
 
         // Destroy EGLContext and terminate display.
-        EGL14.eglDestroyContext(mEglDisplay, mEglContext);
-        EGL14.eglTerminate(mEglDisplay);
-        EGL14.eglReleaseThread();
+        if (!Objects.equals(mEglContext, EGL14.EGL_NO_CONTEXT)) {
+            EGL14.eglDestroyContext(mEglDisplay, mEglContext);
+            mEglContext = EGL14.EGL_NO_CONTEXT;
+        }
+        if (!Objects.equals(mEglDisplay, EGL14.EGL_NO_DISPLAY)) {
+            EGL14.eglTerminate(mEglDisplay);
+            mEglDisplay = EGL14.EGL_NO_DISPLAY;
+        }
 
-        // Clear the created configurations.
-        mEglDisplay = EGL14.EGL_NO_DISPLAY;
-        mEglContext = EGL14.EGL_NO_CONTEXT;
+        EGL14.eglReleaseThread();
         mEglConfig = null;
-        return true;
     }
 
     // --- Private methods ---
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlRenderer.java b/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlRenderer.java
index 984a25a..50488dc 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlRenderer.java
+++ b/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlRenderer.java
@@ -16,12 +16,22 @@
 
 package androidx.camera.effects.opengl;
 
+import static androidx.camera.effects.opengl.Utils.checkGlErrorOrThrow;
+import static androidx.camera.effects.opengl.Utils.configureExternalTexture;
+import static androidx.camera.effects.opengl.Utils.configureTexture2D;
+import static androidx.camera.effects.opengl.Utils.createTextureId;
+import static androidx.core.util.Preconditions.checkState;
+
 import android.graphics.Bitmap;
+import android.opengl.GLES11Ext;
 import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.os.Build;
 import android.util.Size;
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 
 /**
@@ -36,9 +46,27 @@
  *
  * 

It also allows the caller to upload a bitmap and overlay it when rendering to Surface. */ +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RestrictTo(RestrictTo.Scope.LIBRARY) public final class GlRenderer { + private static final String TAG = "GlRenderer"; + + private boolean mInitialized = false; + + private Thread mGlThread = null; + private final GlContext mGlContext = new GlContext(); + private final GlProgramOverlay mGlProgramOverlay = new GlProgramOverlay(); + private final GlProgramCopy mGlProgramCopy = new GlProgramCopy(); + + // Texture IDs. + private int mInputTextureId = -1; + private int mOverlayTextureId = -1; + @NonNull + private int[] mQueueTextureIds = new int[0]; + private int mQueueTextureWidth = -1; + private int mQueueTextureHeight = -1; + // --- Public methods --- /** @@ -46,8 +74,22 @@ * *

Must be called before any other methods. */ - void init() { - throw new UnsupportedOperationException("TODO: implement this"); + public void init() { + checkState(!mInitialized, "Already initialized"); + mInitialized = true; + mGlThread = Thread.currentThread(); + try { + mGlContext.init(); + mGlProgramCopy.init(); + mGlProgramOverlay.init(); + mInputTextureId = createTextureId(); + configureExternalTexture(mInputTextureId); + mOverlayTextureId = createTextureId(); + configureTexture2D(mOverlayTextureId); + } catch (IllegalStateException | IllegalArgumentException e) { + release(); + throw e; + } } /** @@ -55,15 +97,41 @@ * *

Once released, it can never be accessed again. */ - void release() { - throw new UnsupportedOperationException("TODO: implement this"); + public void release() { + checkGlThreadAndInitialized(); + + mInitialized = false; + mGlThread = null; + mQueueTextureWidth = -1; + mQueueTextureHeight = -1; + + mGlContext.release(); + mGlProgramOverlay.release(); + mGlProgramCopy.release(); + + if (mInputTextureId != -1) { + GLES20.glDeleteTextures(1, new int[]{mInputTextureId}, 0); + checkGlErrorOrThrow("glDeleteTextures"); + mInputTextureId = -1; + } + if (mOverlayTextureId != -1) { + GLES20.glDeleteTextures(1, new int[]{mOverlayTextureId}, 0); + checkGlErrorOrThrow("glDeleteTextures"); + mOverlayTextureId = -1; + } + if (mQueueTextureIds.length > 0) { + GLES20.glDeleteTextures(mQueueTextureIds.length, mQueueTextureIds, 0); + checkGlErrorOrThrow("glDeleteTextures"); + mQueueTextureIds = new int[0]; + } } /** * Gets the external input texture ID created during initialization. */ public int getInputTextureId() { - throw new UnsupportedOperationException("TODO: implement this"); + checkGlThreadAndInitialized(); + return mInputTextureId; } /** @@ -79,14 +147,48 @@ */ @NonNull public int[] createBufferTextureIds(int queueDepth, @NonNull Size size) { - throw new UnsupportedOperationException("TODO: implement this"); + checkGlThreadAndInitialized(); + // Delete the current buffer if it exists. + if (mQueueTextureIds.length > 0) { + GLES20.glDeleteTextures(mQueueTextureIds.length, mQueueTextureIds, 0); + checkGlErrorOrThrow("glDeleteTextures"); + } + + mQueueTextureIds = new int[queueDepth]; + // If the queue depth is 0, return an empty array. There is no need to create textures. + if (queueDepth == 0) { + return mQueueTextureIds; + } + + // Create the textures. + GLES20.glGenTextures(queueDepth, mQueueTextureIds, 0); + checkGlErrorOrThrow("glGenTextures"); + mQueueTextureWidth = size.getWidth(); + mQueueTextureHeight = size.getHeight(); + for (int textureId : mQueueTextureIds) { + configureTexture2D(textureId); + GLES20.glTexImage2D( + GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, size.getWidth(), size.getHeight(), 0, + GLES20.GL_RGB, + GLES20.GL_UNSIGNED_BYTE, + null + ); + } + return mQueueTextureIds; } /** * Uploads the {@link Bitmap} to the overlay texture. */ public void uploadOverlay(@NonNull Bitmap overlay) { - throw new UnsupportedOperationException("TODO: implement this"); + checkGlThreadAndInitialized(); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE1); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOverlayTextureId); + checkGlErrorOrThrow("glBindTexture"); + + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, overlay, 0); + checkGlErrorOrThrow("texImage2D"); } /** @@ -96,7 +198,8 @@ * {@link #renderQueueTextureToSurface}. */ public void registerOutputSurface(@NonNull Surface surface) { - throw new UnsupportedOperationException("TODO: implement this"); + checkGlThreadAndInitialized(); + mGlContext.registerSurface(surface); } /** @@ -106,7 +209,8 @@ * {@link #renderQueueTextureToSurface} with the {@link Surface} throws an exception. */ public void unregisterOutputSurface(@NonNull Surface surface) { - throw new UnsupportedOperationException("TODO: implement this"); + checkGlThreadAndInitialized(); + mGlContext.unregisterSurface(surface); } /** @@ -117,7 +221,9 @@ */ public void renderInputToSurface(long timestampNs, @NonNull float[] textureTransform, @NonNull Surface surface) { - throw new UnsupportedOperationException("TODO: implement this"); + checkGlThreadAndInitialized(); + mGlProgramOverlay.draw(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mInputTextureId, + mOverlayTextureId, textureTransform, mGlContext, surface, timestampNs); } /** @@ -128,9 +234,9 @@ * {@link #createBufferTextureIds}. */ public void renderQueueTextureToSurface(int textureId, long timestampNs, - @NonNull float[] textureTransform, - @NonNull Surface surface) { - throw new UnsupportedOperationException("TODO: implement this"); + @NonNull float[] textureTransform, @NonNull Surface surface) { + mGlProgramOverlay.draw(GLES20.GL_TEXTURE_2D, textureId, mOverlayTextureId, + textureTransform, mGlContext, surface, timestampNs); } /** @@ -139,8 +245,14 @@ *

The texture ID must be from the latest return value of{@link #createBufferTextureIds}. */ public void renderInputToQueueTexture(int textureId) { - throw new UnsupportedOperationException("TODO: implement this"); + mGlProgramCopy.draw(mInputTextureId, textureId, mQueueTextureWidth, mQueueTextureHeight); } // --- Private methods --- + + private void checkGlThreadAndInitialized() { + checkState(mInitialized, "OpenGlRenderer is not initialized"); + checkState(mGlThread == Thread.currentThread(), + "Method call must be called on the GL thread."); + } }

diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/Utils.java b/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/Utils.java
index 1a4f66c..e8f2d49 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/Utils.java
+++ b/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/Utils.java
@@ -19,6 +19,7 @@
 import static androidx.camera.effects.opengl.GlProgram.VERTEX_SIZE;
 
 import android.opengl.EGL14;
+import android.opengl.GLES11Ext;
 import android.opengl.GLES20;
 
 import androidx.annotation.NonNull;
@@ -87,4 +88,47 @@
             throw new IllegalStateException("Unable to locate '" + label + "' in program");
         }
     }
+
+    /**
+     * Creates a single texture ID.
+     */
+    static int createTextureId() {
+        int[] textureIds = new int[1];
+        GLES20.glGenTextures(1, textureIds, 0);
+        checkGlErrorOrThrow("glGenTextures");
+        return textureIds[0];
+    }
+
+    /**
+     * Configures the texture as a 2D texture.
+     */
+    static void configureTexture2D(int textureId) {
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
+                GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
+                GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
+                GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
+                GLES20.GL_CLAMP_TO_EDGE);
+    }
+
+    /**
+     * Configures the texture as an external texture.
+     */
+    static void configureExternalTexture(int textureId) {
+        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
+        checkGlErrorOrThrow("glBindTexture " + textureId);
+        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
+                GLES20.GL_NEAREST);
+        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
+                GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
+                GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
+                GLES20.GL_CLAMP_TO_EDGE);
+        checkGlErrorOrThrow("glTexParameter");
+    }
 }
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index 4261c54..16b4531 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -133,7 +133,16 @@
   }
 
   public final class FloatListKt {
+    method public static androidx.collection.FloatList emptyFloatList();
+    method public static androidx.collection.FloatList floatListOf();
+    method public static androidx.collection.FloatList floatListOf(float element1);
+    method public static androidx.collection.FloatList floatListOf(float element1, float element2);
+    method public static androidx.collection.FloatList floatListOf(float element1, float element2, float element3);
+    method public static androidx.collection.FloatList floatListOf(float... elements);
     method public static inline androidx.collection.MutableFloatList mutableFloatListOf();
+    method public static androidx.collection.MutableFloatList mutableFloatListOf(float element1);
+    method public static androidx.collection.MutableFloatList mutableFloatListOf(float element1, float element2);
+    method public static androidx.collection.MutableFloatList mutableFloatListOf(float element1, float element2, float element3);
     method public static inline androidx.collection.MutableFloatList mutableFloatListOf(float... elements);
   }
 
@@ -158,7 +167,15 @@
 
   public final class FloatSetKt {
     method public static androidx.collection.FloatSet emptyFloatSet();
+    method public static androidx.collection.FloatSet floatSetOf();
+    method public static androidx.collection.FloatSet floatSetOf(float element1);
+    method public static androidx.collection.FloatSet floatSetOf(float element1, float element2);
+    method public static androidx.collection.FloatSet floatSetOf(float element1, float element2, float element3);
+    method public static androidx.collection.FloatSet floatSetOf(float... elements);
     method public static androidx.collection.MutableFloatSet mutableFloatSetOf();
+    method public static androidx.collection.MutableFloatSet mutableFloatSetOf(float element1);
+    method public static androidx.collection.MutableFloatSet mutableFloatSetOf(float element1, float element2);
+    method public static androidx.collection.MutableFloatSet mutableFloatSetOf(float element1, float element2, float element3);
     method public static androidx.collection.MutableFloatSet mutableFloatSetOf(float... elements);
   }
 
@@ -201,7 +218,16 @@
   }
 
   public final class IntListKt {
+    method public static androidx.collection.IntList emptyIntList();
+    method public static androidx.collection.IntList intListOf();
+    method public static androidx.collection.IntList intListOf(int element1);
+    method public static androidx.collection.IntList intListOf(int element1, int element2);
+    method public static androidx.collection.IntList intListOf(int element1, int element2, int element3);
+    method public static androidx.collection.IntList intListOf(int... elements);
     method public static inline androidx.collection.MutableIntList mutableIntListOf();
+    method public static androidx.collection.MutableIntList mutableIntListOf(int element1);
+    method public static androidx.collection.MutableIntList mutableIntListOf(int element1, int element2);
+    method public static androidx.collection.MutableIntList mutableIntListOf(int element1, int element2, int element3);
     method public static inline androidx.collection.MutableIntList mutableIntListOf(int... elements);
   }
 
@@ -226,7 +252,15 @@
 
   public final class IntSetKt {
     method public static androidx.collection.IntSet emptyIntSet();
+    method public static androidx.collection.IntSet intSetOf();
+    method public static androidx.collection.IntSet intSetOf(int element1);
+    method public static androidx.collection.IntSet intSetOf(int element1, int element2);
+    method public static androidx.collection.IntSet intSetOf(int element1, int element2, int element3);
+    method public static androidx.collection.IntSet intSetOf(int... elements);
     method public static androidx.collection.MutableIntSet mutableIntSetOf();
+    method public static androidx.collection.MutableIntSet mutableIntSetOf(int element1);
+    method public static androidx.collection.MutableIntSet mutableIntSetOf(int element1, int element2);
+    method public static androidx.collection.MutableIntSet mutableIntSetOf(int element1, int element2, int element3);
     method public static androidx.collection.MutableIntSet mutableIntSetOf(int... elements);
   }
 
@@ -269,7 +303,16 @@
   }
 
   public final class LongListKt {
+    method public static androidx.collection.LongList emptyLongList();
+    method public static androidx.collection.LongList longListOf();
+    method public static androidx.collection.LongList longListOf(long element1);
+    method public static androidx.collection.LongList longListOf(long element1, long element2);
+    method public static androidx.collection.LongList longListOf(long element1, long element2, long element3);
+    method public static androidx.collection.LongList longListOf(long... elements);
     method public static inline androidx.collection.MutableLongList mutableLongListOf();
+    method public static androidx.collection.MutableLongList mutableLongListOf(long element1);
+    method public static androidx.collection.MutableLongList mutableLongListOf(long element1, long element2);
+    method public static androidx.collection.MutableLongList mutableLongListOf(long element1, long element2, long element3);
     method public static inline androidx.collection.MutableLongList mutableLongListOf(long... elements);
   }
 
@@ -294,7 +337,15 @@
 
   public final class LongSetKt {
     method public static androidx.collection.LongSet emptyLongSet();
+    method public static androidx.collection.LongSet longSetOf();
+    method public static androidx.collection.LongSet longSetOf(long element1);
+    method public static androidx.collection.LongSet longSetOf(long element1, long element2);
+    method public static androidx.collection.LongSet longSetOf(long element1, long element2, long element3);
+    method public static androidx.collection.LongSet longSetOf(long... elements);
     method public static androidx.collection.MutableLongSet mutableLongSetOf();
+    method public static androidx.collection.MutableLongSet mutableLongSetOf(long element1);
+    method public static androidx.collection.MutableLongSet mutableLongSetOf(long element1, long element2);
+    method public static androidx.collection.MutableLongSet mutableLongSetOf(long element1, long element2, long element3);
     method public static androidx.collection.MutableLongSet mutableLongSetOf(long... elements);
   }
 
@@ -652,7 +703,15 @@
   public final class ScatterSetKt {
     method public static  androidx.collection.ScatterSet emptyScatterSet();
     method public static  androidx.collection.MutableScatterSet mutableScatterSetOf();
+    method public static  androidx.collection.MutableScatterSet mutableScatterSetOf(E element1);
+    method public static  androidx.collection.MutableScatterSet mutableScatterSetOf(E element1, E element2);
+    method public static  androidx.collection.MutableScatterSet mutableScatterSetOf(E element1, E element2, E element3);
     method public static  androidx.collection.MutableScatterSet mutableScatterSetOf(E?... elements);
+    method public static  androidx.collection.ScatterSet scatterSetOf();
+    method public static  androidx.collection.ScatterSet scatterSetOf(E element1);
+    method public static  androidx.collection.ScatterSet scatterSetOf(E element1, E element2);
+    method public static  androidx.collection.ScatterSet scatterSetOf(E element1, E element2, E element3);
+    method public static  androidx.collection.ScatterSet scatterSetOf(E?... elements);
   }
 
   public class SimpleArrayMap {
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index 7759811..29dd414 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -135,7 +135,16 @@
   }
 
   public final class FloatListKt {
+    method public static androidx.collection.FloatList emptyFloatList();
+    method public static androidx.collection.FloatList floatListOf();
+    method public static androidx.collection.FloatList floatListOf(float element1);
+    method public static androidx.collection.FloatList floatListOf(float element1, float element2);
+    method public static androidx.collection.FloatList floatListOf(float element1, float element2, float element3);
+    method public static androidx.collection.FloatList floatListOf(float... elements);
     method public static inline androidx.collection.MutableFloatList mutableFloatListOf();
+    method public static androidx.collection.MutableFloatList mutableFloatListOf(float element1);
+    method public static androidx.collection.MutableFloatList mutableFloatListOf(float element1, float element2);
+    method public static androidx.collection.MutableFloatList mutableFloatListOf(float element1, float element2, float element3);
     method public static inline androidx.collection.MutableFloatList mutableFloatListOf(float... elements);
   }
 
@@ -163,7 +172,15 @@
 
   public final class FloatSetKt {
     method public static androidx.collection.FloatSet emptyFloatSet();
+    method public static androidx.collection.FloatSet floatSetOf();
+    method public static androidx.collection.FloatSet floatSetOf(float element1);
+    method public static androidx.collection.FloatSet floatSetOf(float element1, float element2);
+    method public static androidx.collection.FloatSet floatSetOf(float element1, float element2, float element3);
+    method public static androidx.collection.FloatSet floatSetOf(float... elements);
     method public static androidx.collection.MutableFloatSet mutableFloatSetOf();
+    method public static androidx.collection.MutableFloatSet mutableFloatSetOf(float element1);
+    method public static androidx.collection.MutableFloatSet mutableFloatSetOf(float element1, float element2);
+    method public static androidx.collection.MutableFloatSet mutableFloatSetOf(float element1, float element2, float element3);
     method public static androidx.collection.MutableFloatSet mutableFloatSetOf(float... elements);
   }
 
@@ -208,7 +225,16 @@
   }
 
   public final class IntListKt {
+    method public static androidx.collection.IntList emptyIntList();
+    method public static androidx.collection.IntList intListOf();
+    method public static androidx.collection.IntList intListOf(int element1);
+    method public static androidx.collection.IntList intListOf(int element1, int element2);
+    method public static androidx.collection.IntList intListOf(int element1, int element2, int element3);
+    method public static androidx.collection.IntList intListOf(int... elements);
     method public static inline androidx.collection.MutableIntList mutableIntListOf();
+    method public static androidx.collection.MutableIntList mutableIntListOf(int element1);
+    method public static androidx.collection.MutableIntList mutableIntListOf(int element1, int element2);
+    method public static androidx.collection.MutableIntList mutableIntListOf(int element1, int element2, int element3);
     method public static inline androidx.collection.MutableIntList mutableIntListOf(int... elements);
   }
 
@@ -236,7 +262,15 @@
 
   public final class IntSetKt {
     method public static androidx.collection.IntSet emptyIntSet();
+    method public static androidx.collection.IntSet intSetOf();
+    method public static androidx.collection.IntSet intSetOf(int element1);
+    method public static androidx.collection.IntSet intSetOf(int element1, int element2);
+    method public static androidx.collection.IntSet intSetOf(int element1, int element2, int element3);
+    method public static androidx.collection.IntSet intSetOf(int... elements);
     method public static androidx.collection.MutableIntSet mutableIntSetOf();
+    method public static androidx.collection.MutableIntSet mutableIntSetOf(int element1);
+    method public static androidx.collection.MutableIntSet mutableIntSetOf(int element1, int element2);
+    method public static androidx.collection.MutableIntSet mutableIntSetOf(int element1, int element2, int element3);
     method public static androidx.collection.MutableIntSet mutableIntSetOf(int... elements);
   }
 
@@ -281,7 +315,16 @@
   }
 
   public final class LongListKt {
+    method public static androidx.collection.LongList emptyLongList();
+    method public static androidx.collection.LongList longListOf();
+    method public static androidx.collection.LongList longListOf(long element1);
+    method public static androidx.collection.LongList longListOf(long element1, long element2);
+    method public static androidx.collection.LongList longListOf(long element1, long element2, long element3);
+    method public static androidx.collection.LongList longListOf(long... elements);
     method public static inline androidx.collection.MutableLongList mutableLongListOf();
+    method public static androidx.collection.MutableLongList mutableLongListOf(long element1);
+    method public static androidx.collection.MutableLongList mutableLongListOf(long element1, long element2);
+    method public static androidx.collection.MutableLongList mutableLongListOf(long element1, long element2, long element3);
     method public static inline androidx.collection.MutableLongList mutableLongListOf(long... elements);
   }
 
@@ -309,7 +352,15 @@
 
   public final class LongSetKt {
     method public static androidx.collection.LongSet emptyLongSet();
+    method public static androidx.collection.LongSet longSetOf();
+    method public static androidx.collection.LongSet longSetOf(long element1);
+    method public static androidx.collection.LongSet longSetOf(long element1, long element2);
+    method public static androidx.collection.LongSet longSetOf(long element1, long element2, long element3);
+    method public static androidx.collection.LongSet longSetOf(long... elements);
     method public static androidx.collection.MutableLongSet mutableLongSetOf();
+    method public static androidx.collection.MutableLongSet mutableLongSetOf(long element1);
+    method public static androidx.collection.MutableLongSet mutableLongSetOf(long element1, long element2);
+    method public static androidx.collection.MutableLongSet mutableLongSetOf(long element1, long element2, long element3);
     method public static androidx.collection.MutableLongSet mutableLongSetOf(long... elements);
   }
 
@@ -682,7 +733,15 @@
   public final class ScatterSetKt {
     method public static  androidx.collection.ScatterSet emptyScatterSet();
     method public static  androidx.collection.MutableScatterSet mutableScatterSetOf();
+    method public static  androidx.collection.MutableScatterSet mutableScatterSetOf(E element1);
+    method public static  androidx.collection.MutableScatterSet mutableScatterSetOf(E element1, E element2);
+    method public static  androidx.collection.MutableScatterSet mutableScatterSetOf(E element1, E element2, E element3);
     method public static  androidx.collection.MutableScatterSet mutableScatterSetOf(E?... elements);
+    method public static  androidx.collection.ScatterSet scatterSetOf();
+    method public static  androidx.collection.ScatterSet scatterSetOf(E element1);
+    method public static  androidx.collection.ScatterSet scatterSetOf(E element1, E element2);
+    method public static  androidx.collection.ScatterSet scatterSetOf(E element1, E element2, E element3);
+    method public static  androidx.collection.ScatterSet scatterSetOf(E?... elements);
   }
 
   public class SimpleArrayMap {
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
index 5aa24f6..cf0c648 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
@@ -839,13 +839,80 @@
 @Suppress("PrivatePropertyName")
 private val EmptyFloatArray = FloatArray(0)
 
+private val EmptyFloatList: FloatList = MutableFloatList(0)
+
 /**
- * Creates and returns an empty [MutableFloatList] with the default capacity.
+ * @return a read-only [FloatList] with nothing in it.
+ */
+public fun emptyFloatList(): FloatList = EmptyFloatList
+
+/**
+ * @return a read-only [FloatList] with nothing in it.
+ */
+public fun floatListOf(): FloatList = EmptyFloatList
+
+/**
+ * @return a new read-only [FloatList] with [element1] as the only item in the list.
+ */
+public fun floatListOf(element1: Float): FloatList = mutableFloatListOf(element1)
+
+/**
+ * @return a new read-only [FloatList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun floatListOf(element1: Float, element2: Float): FloatList =
+    mutableFloatListOf(element1, element2)
+
+/**
+ * @return a new read-only [FloatList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun floatListOf(element1: Float, element2: Float, element3: Float): FloatList =
+    mutableFloatListOf(element1, element2, element3)
+
+/**
+ * @return a new read-only [FloatList] with [elements] in order.
+ */
+public fun floatListOf(vararg elements: Float): FloatList =
+    MutableFloatList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * @return a new empty [MutableFloatList] with the default capacity.
  */
 public inline fun mutableFloatListOf(): MutableFloatList = MutableFloatList()
 
 /**
- * Creates and returns a [MutableFloatList] with the given values.
+ * @return a new [MutableFloatList] with [element1] as the only item in the list.
+ */
+public fun mutableFloatListOf(element1: Float): MutableFloatList {
+    val list = MutableFloatList(1)
+    list += element1
+    return list
+}
+
+/**
+ * @return a new [MutableFloatList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun mutableFloatListOf(element1: Float, element2: Float): MutableFloatList {
+    val list = MutableFloatList(2)
+    list += element1
+    list += element2
+    return list
+}
+
+/**
+ * @return a new [MutableFloatList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun mutableFloatListOf(element1: Float, element2: Float, element3: Float): MutableFloatList {
+    val list = MutableFloatList(3)
+    list += element1
+    list += element2
+    list += element3
+    return list
+}
+
+/**
+ * @return a new [MutableFloatList] with the given elements, in order.
  */
 public inline fun mutableFloatListOf(vararg elements: Float): MutableFloatList =
-    MutableFloatList(elements.size).also { it.addAll(elements) }
+    MutableFloatList(elements.size).apply { plusAssign(elements) }
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
index ec17dcb..278902c4 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
@@ -43,17 +43,75 @@
 public fun emptyFloatSet(): FloatSet = EmptyFloatSet
 
 /**
+ * Returns an empty, read-only [ScatterSet].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun floatSetOf(): FloatSet = EmptyFloatSet
+
+/**
+ * Returns a new read-only [FloatSet] with only [element1] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun floatSetOf(element1: Float): FloatSet = mutableFloatSetOf(element1)
+
+/**
+ * Returns a new read-only [FloatSet] with only [element1] and [element2] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun floatSetOf(element1: Float, element2: Float): FloatSet =
+    mutableFloatSetOf(element1, element2)
+
+/**
+ * Returns a new read-only [FloatSet] with only [element1], [element2], and [element3] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun floatSetOf(element1: Float, element2: Float, element3: Float): FloatSet =
+    mutableFloatSetOf(element1, element2, element3)
+
+/**
+ * Returns a new read-only [FloatSet] with only [elements] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun floatSetOf(vararg elements: Float): FloatSet =
+    MutableFloatSet(elements.size).apply { plusAssign(elements) }
+
+/**
  * Returns a new [MutableFloatSet].
  */
 public fun mutableFloatSetOf(): MutableFloatSet = MutableFloatSet()
 
 /**
+ * Returns a new [MutableFloatSet] with only [element1] in it.
+ */
+public fun mutableFloatSetOf(element1: Float): MutableFloatSet =
+    MutableFloatSet(1).apply {
+        plusAssign(element1)
+    }
+
+/**
+ * Returns a new [MutableFloatSet] with only [element1] and [element2] in it.
+ */
+public fun mutableFloatSetOf(element1: Float, element2: Float): MutableFloatSet =
+    MutableFloatSet(2).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+    }
+
+/**
+ * Returns a new [MutableFloatSet] with only [element1], [element2], and [element3] in it.
+ */
+public fun mutableFloatSetOf(element1: Float, element2: Float, element3: Float): MutableFloatSet =
+    MutableFloatSet(3).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+        plusAssign(element3)
+    }
+
+/**
  * Returns a new [MutableFloatSet] with the specified elements.
  */
 public fun mutableFloatSetOf(vararg elements: Float): MutableFloatSet =
-    MutableFloatSet(elements.size).apply {
-        addAll(elements)
-    }
+    MutableFloatSet(elements.size).apply { plusAssign(elements) }
 
 /**
  * [FloatSet] is a container with a [Set]-like interface designed to avoid
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
index dbeb31b..8c6122db 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
@@ -483,7 +483,7 @@
  * @constructor Creates a [MutableIntList] with a [capacity] of `initialCapacity`.
  */
 public class MutableIntList(
-    initialCapacity: Int = DefaultCapacity
+    initialCapacity: Int = 16
 ) : IntList(initialCapacity) {
     /**
      * Returns the total number of elements that can be held before the [MutableIntList] must
@@ -835,20 +835,84 @@
     }
 }
 
-@Suppress("ConstPropertyName")
-private const val DefaultCapacity = 16
-
 // Empty array used when nothing is allocated
 @Suppress("PrivatePropertyName")
 private val EmptyIntArray = IntArray(0)
 
+private val EmptyIntList: IntList = MutableIntList(0)
+
 /**
- * Creates and returns an empty [MutableIntList] with the default capacity.
+ * @return a read-only [IntList] with nothing in it.
+ */
+public fun emptyIntList(): IntList = EmptyIntList
+
+/**
+ * @return a read-only [IntList] with nothing in it.
+ */
+public fun intListOf(): IntList = EmptyIntList
+
+/**
+ * @return a new read-only [IntList] with [element1] as the only item in the list.
+ */
+public fun intListOf(element1: Int): IntList = mutableIntListOf(element1)
+
+/**
+ * @return a new read-only [IntList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun intListOf(element1: Int, element2: Int): IntList =
+    mutableIntListOf(element1, element2)
+
+/**
+ * @return a new read-only [IntList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun intListOf(element1: Int, element2: Int, element3: Int): IntList =
+    mutableIntListOf(element1, element2, element3)
+
+/**
+ * @return a new read-only [IntList] with [elements] in order.
+ */
+public fun intListOf(vararg elements: Int): IntList =
+    MutableIntList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * @return a new empty [MutableIntList] with the default capacity.
  */
 public inline fun mutableIntListOf(): MutableIntList = MutableIntList()
 
 /**
- * Creates and returns a [MutableIntList] with the given values.
+ * @return a new [MutableIntList] with [element1] as the only item in the list.
+ */
+public fun mutableIntListOf(element1: Int): MutableIntList {
+    val list = MutableIntList(1)
+    list += element1
+    return list
+}
+
+/**
+ * @return a new [MutableIntList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun mutableIntListOf(element1: Int, element2: Int): MutableIntList {
+    val list = MutableIntList(2)
+    list += element1
+    list += element2
+    return list
+}
+
+/**
+ * @return a new [MutableIntList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun mutableIntListOf(element1: Int, element2: Int, element3: Int): MutableIntList {
+    val list = MutableIntList(3)
+    list += element1
+    list += element2
+    list += element3
+    return list
+}
+
+/**
+ * @return a new [MutableIntList] with the given elements, in order.
  */
 public inline fun mutableIntListOf(vararg elements: Int): MutableIntList =
-    MutableIntList(elements.size).also { it.addAll(elements) }
+    MutableIntList(elements.size).apply { plusAssign(elements) }
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
index fc29782..2db0f7f 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
@@ -34,7 +34,7 @@
 // Default empty set to avoid allocations
 private val EmptyIntSet = MutableIntSet(0)
 
-// An empty array of Ints
+// An empty array of ints
 private val EmptyIntArray = IntArray(0)
 
 /**
@@ -43,17 +43,75 @@
 public fun emptyIntSet(): IntSet = EmptyIntSet
 
 /**
+ * Returns an empty, read-only [ScatterSet].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun intSetOf(): IntSet = EmptyIntSet
+
+/**
+ * Returns a new read-only [IntSet] with only [element1] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun intSetOf(element1: Int): IntSet = mutableIntSetOf(element1)
+
+/**
+ * Returns a new read-only [IntSet] with only [element1] and [element2] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun intSetOf(element1: Int, element2: Int): IntSet =
+    mutableIntSetOf(element1, element2)
+
+/**
+ * Returns a new read-only [IntSet] with only [element1], [element2], and [element3] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun intSetOf(element1: Int, element2: Int, element3: Int): IntSet =
+    mutableIntSetOf(element1, element2, element3)
+
+/**
+ * Returns a new read-only [IntSet] with only [elements] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun intSetOf(vararg elements: Int): IntSet =
+    MutableIntSet(elements.size).apply { plusAssign(elements) }
+
+/**
  * Returns a new [MutableIntSet].
  */
 public fun mutableIntSetOf(): MutableIntSet = MutableIntSet()
 
 /**
+ * Returns a new [MutableIntSet] with only [element1] in it.
+ */
+public fun mutableIntSetOf(element1: Int): MutableIntSet =
+    MutableIntSet(1).apply {
+        plusAssign(element1)
+    }
+
+/**
+ * Returns a new [MutableIntSet] with only [element1] and [element2] in it.
+ */
+public fun mutableIntSetOf(element1: Int, element2: Int): MutableIntSet =
+    MutableIntSet(2).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+    }
+
+/**
+ * Returns a new [MutableIntSet] with only [element1], [element2], and [element3] in it.
+ */
+public fun mutableIntSetOf(element1: Int, element2: Int, element3: Int): MutableIntSet =
+    MutableIntSet(3).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+        plusAssign(element3)
+    }
+
+/**
  * Returns a new [MutableIntSet] with the specified elements.
  */
 public fun mutableIntSetOf(vararg elements: Int): MutableIntSet =
-    MutableIntSet(elements.size).apply {
-        addAll(elements)
-    }
+    MutableIntSet(elements.size).apply { plusAssign(elements) }
 
 /**
  * [IntSet] is a container with a [Set]-like interface designed to avoid
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
index 85679d4..94dfd82 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
@@ -483,7 +483,7 @@
  * @constructor Creates a [MutableLongList] with a [capacity] of `initialCapacity`.
  */
 public class MutableLongList(
-    initialCapacity: Int = DefaultCapacity
+    initialCapacity: Int = 16
 ) : LongList(initialCapacity) {
     /**
      * Returns the total number of elements that can be held before the [MutableLongList] must
@@ -835,20 +835,84 @@
     }
 }
 
-@Suppress("ConstPropertyName")
-private const val DefaultCapacity = 16
-
 // Empty array used when nothing is allocated
 @Suppress("PrivatePropertyName")
 private val EmptyLongArray = LongArray(0)
 
+private val EmptyLongList: LongList = MutableLongList(0)
+
 /**
- * Creates and returns an empty [MutableLongList] with the default capacity.
+ * @return a read-only [LongList] with nothing in it.
+ */
+public fun emptyLongList(): LongList = EmptyLongList
+
+/**
+ * @return a read-only [LongList] with nothing in it.
+ */
+public fun longListOf(): LongList = EmptyLongList
+
+/**
+ * @return a new read-only [LongList] with [element1] as the only item in the list.
+ */
+public fun longListOf(element1: Long): LongList = mutableLongListOf(element1)
+
+/**
+ * @return a new read-only [LongList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun longListOf(element1: Long, element2: Long): LongList =
+    mutableLongListOf(element1, element2)
+
+/**
+ * @return a new read-only [LongList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun longListOf(element1: Long, element2: Long, element3: Long): LongList =
+    mutableLongListOf(element1, element2, element3)
+
+/**
+ * @return a new read-only [LongList] with [elements] in order.
+ */
+public fun longListOf(vararg elements: Long): LongList =
+    MutableLongList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * @return a new empty [MutableLongList] with the default capacity.
  */
 public inline fun mutableLongListOf(): MutableLongList = MutableLongList()
 
 /**
- * Creates and returns a [MutableLongList] with the given values.
+ * @return a new [MutableLongList] with [element1] as the only item in the list.
+ */
+public fun mutableLongListOf(element1: Long): MutableLongList {
+    val list = MutableLongList(1)
+    list += element1
+    return list
+}
+
+/**
+ * @return a new [MutableLongList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun mutableLongListOf(element1: Long, element2: Long): MutableLongList {
+    val list = MutableLongList(2)
+    list += element1
+    list += element2
+    return list
+}
+
+/**
+ * @return a new [MutableLongList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun mutableLongListOf(element1: Long, element2: Long, element3: Long): MutableLongList {
+    val list = MutableLongList(3)
+    list += element1
+    list += element2
+    list += element3
+    return list
+}
+
+/**
+ * @return a new [MutableLongList] with the given elements, in order.
  */
 public inline fun mutableLongListOf(vararg elements: Long): MutableLongList =
-    MutableLongList(elements.size).also { it.addAll(elements) }
+    MutableLongList(elements.size).apply { plusAssign(elements) }
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
index 4cfe132..f292716 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
@@ -34,7 +34,7 @@
 // Default empty set to avoid allocations
 private val EmptyLongSet = MutableLongSet(0)
 
-// An empty array of Longs
+// An empty array of longs
 private val EmptyLongArray = LongArray(0)
 
 /**
@@ -43,17 +43,75 @@
 public fun emptyLongSet(): LongSet = EmptyLongSet
 
 /**
+ * Returns an empty, read-only [ScatterSet].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun longSetOf(): LongSet = EmptyLongSet
+
+/**
+ * Returns a new read-only [LongSet] with only [element1] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun longSetOf(element1: Long): LongSet = mutableLongSetOf(element1)
+
+/**
+ * Returns a new read-only [LongSet] with only [element1] and [element2] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun longSetOf(element1: Long, element2: Long): LongSet =
+    mutableLongSetOf(element1, element2)
+
+/**
+ * Returns a new read-only [LongSet] with only [element1], [element2], and [element3] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun longSetOf(element1: Long, element2: Long, element3: Long): LongSet =
+    mutableLongSetOf(element1, element2, element3)
+
+/**
+ * Returns a new read-only [LongSet] with only [elements] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun longSetOf(vararg elements: Long): LongSet =
+    MutableLongSet(elements.size).apply { plusAssign(elements) }
+
+/**
  * Returns a new [MutableLongSet].
  */
 public fun mutableLongSetOf(): MutableLongSet = MutableLongSet()
 
 /**
+ * Returns a new [MutableLongSet] with only [element1] in it.
+ */
+public fun mutableLongSetOf(element1: Long): MutableLongSet =
+    MutableLongSet(1).apply {
+        plusAssign(element1)
+    }
+
+/**
+ * Returns a new [MutableLongSet] with only [element1] and [element2] in it.
+ */
+public fun mutableLongSetOf(element1: Long, element2: Long): MutableLongSet =
+    MutableLongSet(2).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+    }
+
+/**
+ * Returns a new [MutableLongSet] with only [element1], [element2], and [element3] in it.
+ */
+public fun mutableLongSetOf(element1: Long, element2: Long, element3: Long): MutableLongSet =
+    MutableLongSet(3).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+        plusAssign(element3)
+    }
+
+/**
  * Returns a new [MutableLongSet] with the specified elements.
  */
 public fun mutableLongSetOf(vararg elements: Long): MutableLongSet =
-    MutableLongSet(elements.size).apply {
-        addAll(elements)
-    }
+    MutableLongSet(elements.size).apply { plusAssign(elements) }
 
 /**
  * [LongSet] is a container with a [Set]-like interface designed to avoid
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
index 3b13f66..2eac81f 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
@@ -42,17 +42,75 @@
 public fun  emptyScatterSet(): ScatterSet = EmptyScatterSet as ScatterSet
 
 /**
+ * Returns an empty, read-only [ScatterSet].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun  scatterSetOf(): ScatterSet = EmptyScatterSet as ScatterSet
+
+/**
+ * Returns a new read-only [ScatterSet] with only [element1] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun  scatterSetOf(element1: E): ScatterSet = mutableScatterSetOf(element1)
+
+/**
+ * Returns a new read-only [ScatterSet] with only [element1] and [element2] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun  scatterSetOf(element1: E, element2: E): ScatterSet =
+    mutableScatterSetOf(element1, element2)
+
+/**
+ * Returns a new read-only [ScatterSet] with only [element1], [element2], and [element3] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun  scatterSetOf(element1: E, element2: E, element3: E): ScatterSet =
+    mutableScatterSetOf(element1, element2, element3)
+
+/**
+ * Returns a new read-only [ScatterSet] with only [elements] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun  scatterSetOf(vararg elements: E): ScatterSet =
+    MutableScatterSet(elements.size).apply { plusAssign(elements) }
+
+/**
  * Returns a new [MutableScatterSet].
  */
 public fun  mutableScatterSetOf(): MutableScatterSet = MutableScatterSet()
 
 /**
+ * Returns a new [MutableScatterSet] with only [element1] in it.
+ */
+public fun  mutableScatterSetOf(element1: E): MutableScatterSet =
+    MutableScatterSet(1).apply {
+        plusAssign(element1)
+    }
+
+/**
+ * Returns a new [MutableScatterSet] with only [element1] and [element2] in it.
+ */
+public fun  mutableScatterSetOf(element1: E, element2: E): MutableScatterSet =
+    MutableScatterSet(2).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+    }
+
+/**
+ * Returns a new [MutableScatterSet] with only [element1], [element2], and [element3] in it.
+ */
+public fun  mutableScatterSetOf(element1: E, element2: E, element3: E): MutableScatterSet =
+    MutableScatterSet(3).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+        plusAssign(element3)
+    }
+
+/**
  * Returns a new [MutableScatterSet] with the specified contents.
  */
 public fun  mutableScatterSetOf(vararg elements: E): MutableScatterSet =
-    MutableScatterSet(elements.size).apply {
-        addAll(elements)
-    }
+    MutableScatterSet(elements.size).apply { plusAssign(elements) }
 
 /**
  * [ScatterSet] is a container with a [Set]-like interface based on a flat
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
index 63d4ce6..b4e501b 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
@@ -629,4 +629,88 @@
         l.sortDescending()
         assertEquals(mutableFloatListOf(5f, 4f, 3f, 2f, 1f), l)
     }
+
+    @Test
+    fun testEmptyFloatList() {
+        val l = emptyFloatList()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun floatListOfEmpty() {
+        val l = floatListOf()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun floatListOfOneValue() {
+        val l = floatListOf(2f)
+        assertEquals(1, l.size)
+        assertEquals(2f, l[0])
+    }
+
+    @Test
+    fun floatListOfTwoValues() {
+        val l = floatListOf(2f, 1f)
+        assertEquals(2, l.size)
+        assertEquals(2f, l[0])
+        assertEquals(1f, l[1])
+    }
+
+    @Test
+    fun floatListOfThreeValues() {
+        val l = floatListOf(2f, 10f, -1f)
+        assertEquals(3, l.size)
+        assertEquals(2f, l[0])
+        assertEquals(10f, l[1])
+        assertEquals(-1f, l[2])
+    }
+
+    @Test
+    fun floatListOfFourValues() {
+        val l = floatListOf(2f, 10f, -1f, 10f)
+        assertEquals(4, l.size)
+        assertEquals(2f, l[0])
+        assertEquals(10f, l[1])
+        assertEquals(-1f, l[2])
+        assertEquals(10f, l[3])
+    }
+
+    @Test
+    fun mutableFloatListOfOneValue() {
+        val l = mutableFloatListOf(2f)
+        assertEquals(1, l.size)
+        assertEquals(1, l.capacity)
+        assertEquals(2f, l[0])
+    }
+
+    @Test
+    fun mutableFloatListOfTwoValues() {
+        val l = mutableFloatListOf(2f, 1f)
+        assertEquals(2, l.size)
+        assertEquals(2, l.capacity)
+        assertEquals(2f, l[0])
+        assertEquals(1f, l[1])
+    }
+
+    @Test
+    fun mutableFloatListOfThreeValues() {
+        val l = mutableFloatListOf(2f, 10f, -1f)
+        assertEquals(3, l.size)
+        assertEquals(3, l.capacity)
+        assertEquals(2f, l[0])
+        assertEquals(10f, l[1])
+        assertEquals(-1f, l[2])
+    }
+
+    @Test
+    fun mutableFloatListOfFourValues() {
+        val l = mutableFloatListOf(2f, 10f, -1f, 10f)
+        assertEquals(4, l.size)
+        assertEquals(4, l.capacity)
+        assertEquals(2f, l[0])
+        assertEquals(10f, l[1])
+        assertEquals(-1f, l[2])
+        assertEquals(10f, l[3])
+    }
 }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
index df1c181..98f7b59 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
@@ -20,6 +20,7 @@
 import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
 import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
 import kotlin.test.assertTrue
 
 class FloatSetTest {
@@ -442,4 +443,84 @@
         assertTrue(set.trim() > 0)
         assertEquals(capacity, set.capacity)
     }
+
+    @Test
+    fun floatSetOfEmpty() {
+        assertSame(emptyFloatSet(), floatSetOf())
+        assertEquals(0, floatSetOf().size)
+    }
+
+    @Test
+    fun floatSetOfOne() {
+        val set = floatSetOf(1f)
+        assertEquals(1, set.size)
+        assertEquals(1f, set.first())
+    }
+
+    @Test
+    fun floatSetOfTwo() {
+        val set = floatSetOf(1f, 2f)
+        assertEquals(2, set.size)
+        assertTrue(1f in set)
+        assertTrue(2f in set)
+        assertFalse(5f in set)
+    }
+
+    @Test
+    fun floatSetOfThree() {
+        val set = floatSetOf(1f, 2f, 3f)
+        assertEquals(3, set.size)
+        assertTrue(1f in set)
+        assertTrue(2f in set)
+        assertTrue(3f in set)
+        assertFalse(5f in set)
+    }
+
+    @Test
+    fun floatSetOfFour() {
+        val set = floatSetOf(1f, 2f, 3f, 4f)
+        assertEquals(4, set.size)
+        assertTrue(1f in set)
+        assertTrue(2f in set)
+        assertTrue(3f in set)
+        assertTrue(4f in set)
+        assertFalse(5f in set)
+    }
+
+    @Test
+    fun mutableFloatSetOfOne() {
+        val set = mutableFloatSetOf(1f)
+        assertEquals(1, set.size)
+        assertEquals(1f, set.first())
+    }
+
+    @Test
+    fun mutableFloatSetOfTwo() {
+        val set = mutableFloatSetOf(1f, 2f)
+        assertEquals(2, set.size)
+        assertTrue(1f in set)
+        assertTrue(2f in set)
+        assertFalse(5f in set)
+    }
+
+    @Test
+    fun mutableFloatSetOfThree() {
+        val set = mutableFloatSetOf(1f, 2f, 3f)
+        assertEquals(3, set.size)
+        assertTrue(1f in set)
+        assertTrue(2f in set)
+        assertTrue(3f in set)
+        assertFalse(5f in set)
+    }
+
+    @Test
+    fun mutableFloatSetOfFour() {
+        val set = mutableFloatSetOf(1f, 2f, 3f, 4f)
+        assertEquals(4, set.size)
+        assertTrue(1f in set)
+        assertTrue(2f in set)
+        assertTrue(3f in set)
+        assertTrue(4f in set)
+        assertFalse(5f in set)
+    }
 }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
index 068527c..66d89af 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
@@ -628,4 +628,88 @@
         l.sortDescending()
         assertEquals(mutableIntListOf(5, 4, 3, 2, 1), l)
     }
+
+    @Test
+    fun testEmptyIntList() {
+        val l = emptyIntList()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun intListOfEmpty() {
+        val l = intListOf()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun intListOfOneValue() {
+        val l = intListOf(2)
+        assertEquals(1, l.size)
+        assertEquals(2, l[0])
+    }
+
+    @Test
+    fun intListOfTwoValues() {
+        val l = intListOf(2, 1)
+        assertEquals(2, l.size)
+        assertEquals(2, l[0])
+        assertEquals(1, l[1])
+    }
+
+    @Test
+    fun intListOfThreeValues() {
+        val l = intListOf(2, 10, -1)
+        assertEquals(3, l.size)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+        assertEquals(-1, l[2])
+    }
+
+    @Test
+    fun intListOfFourValues() {
+        val l = intListOf(2, 10, -1, 10)
+        assertEquals(4, l.size)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+        assertEquals(-1, l[2])
+        assertEquals(10, l[3])
+    }
+
+    @Test
+    fun mutableIntListOfOneValue() {
+        val l = mutableIntListOf(2)
+        assertEquals(1, l.size)
+        assertEquals(1, l.capacity)
+        assertEquals(2, l[0])
+    }
+
+    @Test
+    fun mutableIntListOfTwoValues() {
+        val l = mutableIntListOf(2, 1)
+        assertEquals(2, l.size)
+        assertEquals(2, l.capacity)
+        assertEquals(2, l[0])
+        assertEquals(1, l[1])
+    }
+
+    @Test
+    fun mutableIntListOfThreeValues() {
+        val l = mutableIntListOf(2, 10, -1)
+        assertEquals(3, l.size)
+        assertEquals(3, l.capacity)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+        assertEquals(-1, l[2])
+    }
+
+    @Test
+    fun mutableIntListOfFourValues() {
+        val l = mutableIntListOf(2, 10, -1, 10)
+        assertEquals(4, l.size)
+        assertEquals(4, l.capacity)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+        assertEquals(-1, l[2])
+        assertEquals(10, l[3])
+    }
 }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
index 121b0a6..9d326e1 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
@@ -20,6 +20,7 @@
 import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
 import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
 import kotlin.test.assertTrue
 
 class IntSetTest {
@@ -442,4 +443,84 @@
         assertTrue(set.trim() > 0)
         assertEquals(capacity, set.capacity)
     }
+
+    @Test
+    fun intSetOfEmpty() {
+        assertSame(emptyIntSet(), intSetOf())
+        assertEquals(0, intSetOf().size)
+    }
+
+    @Test
+    fun intSetOfOne() {
+        val set = intSetOf(1)
+        assertEquals(1, set.size)
+        assertEquals(1, set.first())
+    }
+
+    @Test
+    fun intSetOfTwo() {
+        val set = intSetOf(1, 2)
+        assertEquals(2, set.size)
+        assertTrue(1 in set)
+        assertTrue(2 in set)
+        assertFalse(5 in set)
+    }
+
+    @Test
+    fun intSetOfThree() {
+        val set = intSetOf(1, 2, 3)
+        assertEquals(3, set.size)
+        assertTrue(1 in set)
+        assertTrue(2 in set)
+        assertTrue(3 in set)
+        assertFalse(5 in set)
+    }
+
+    @Test
+    fun intSetOfFour() {
+        val set = intSetOf(1, 2, 3, 4)
+        assertEquals(4, set.size)
+        assertTrue(1 in set)
+        assertTrue(2 in set)
+        assertTrue(3 in set)
+        assertTrue(4 in set)
+        assertFalse(5 in set)
+    }
+
+    @Test
+    fun mutableIntSetOfOne() {
+        val set = mutableIntSetOf(1)
+        assertEquals(1, set.size)
+        assertEquals(1, set.first())
+    }
+
+    @Test
+    fun mutableIntSetOfTwo() {
+        val set = mutableIntSetOf(1, 2)
+        assertEquals(2, set.size)
+        assertTrue(1 in set)
+        assertTrue(2 in set)
+        assertFalse(5 in set)
+    }
+
+    @Test
+    fun mutableIntSetOfThree() {
+        val set = mutableIntSetOf(1, 2, 3)
+        assertEquals(3, set.size)
+        assertTrue(1 in set)
+        assertTrue(2 in set)
+        assertTrue(3 in set)
+        assertFalse(5 in set)
+    }
+
+    @Test
+    fun mutableIntSetOfFour() {
+        val set = mutableIntSetOf(1, 2, 3, 4)
+        assertEquals(4, set.size)
+        assertTrue(1 in set)
+        assertTrue(2 in set)
+        assertTrue(3 in set)
+        assertTrue(4 in set)
+        assertFalse(5 in set)
+    }
 }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
index e9893fa..45aa039 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
@@ -628,4 +628,88 @@
         l.sortDescending()
         assertEquals(mutableLongListOf(5L, 4L, 3L, 2L, 1L), l)
     }
+
+    @Test
+    fun testEmptyLongList() {
+        val l = emptyLongList()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun longListOfEmpty() {
+        val l = longListOf()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun longListOfOneValue() {
+        val l = longListOf(2L)
+        assertEquals(1, l.size)
+        assertEquals(2L, l[0])
+    }
+
+    @Test
+    fun longListOfTwoValues() {
+        val l = longListOf(2L, 1L)
+        assertEquals(2, l.size)
+        assertEquals(2L, l[0])
+        assertEquals(1L, l[1])
+    }
+
+    @Test
+    fun longListOfThreeValues() {
+        val l = longListOf(2L, 10L, -1L)
+        assertEquals(3, l.size)
+        assertEquals(2L, l[0])
+        assertEquals(10L, l[1])
+        assertEquals(-1L, l[2])
+    }
+
+    @Test
+    fun longListOfFourValues() {
+        val l = longListOf(2L, 10L, -1L, 10L)
+        assertEquals(4, l.size)
+        assertEquals(2L, l[0])
+        assertEquals(10L, l[1])
+        assertEquals(-1L, l[2])
+        assertEquals(10L, l[3])
+    }
+
+    @Test
+    fun mutableLongListOfOneValue() {
+        val l = mutableLongListOf(2L)
+        assertEquals(1, l.size)
+        assertEquals(1, l.capacity)
+        assertEquals(2L, l[0])
+    }
+
+    @Test
+    fun mutableLongListOfTwoValues() {
+        val l = mutableLongListOf(2L, 1L)
+        assertEquals(2, l.size)
+        assertEquals(2, l.capacity)
+        assertEquals(2L, l[0])
+        assertEquals(1L, l[1])
+    }
+
+    @Test
+    fun mutableLongListOfThreeValues() {
+        val l = mutableLongListOf(2L, 10L, -1L)
+        assertEquals(3, l.size)
+        assertEquals(3, l.capacity)
+        assertEquals(2L, l[0])
+        assertEquals(10L, l[1])
+        assertEquals(-1L, l[2])
+    }
+
+    @Test
+    fun mutableLongListOfFourValues() {
+        val l = mutableLongListOf(2L, 10L, -1L, 10L)
+        assertEquals(4, l.size)
+        assertEquals(4, l.capacity)
+        assertEquals(2L, l[0])
+        assertEquals(10L, l[1])
+        assertEquals(-1L, l[2])
+        assertEquals(10L, l[3])
+    }
 }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
index de7549d..1278fcf 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
@@ -20,6 +20,7 @@
 import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
 import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
 import kotlin.test.assertTrue
 
 class LongSetTest {
@@ -442,4 +443,84 @@
         assertTrue(set.trim() > 0)
         assertEquals(capacity, set.capacity)
     }
+
+    @Test
+    fun longSetOfEmpty() {
+        assertSame(emptyLongSet(), longSetOf())
+        assertEquals(0, longSetOf().size)
+    }
+
+    @Test
+    fun longSetOfOne() {
+        val set = longSetOf(1L)
+        assertEquals(1, set.size)
+        assertEquals(1L, set.first())
+    }
+
+    @Test
+    fun longSetOfTwo() {
+        val set = longSetOf(1L, 2L)
+        assertEquals(2, set.size)
+        assertTrue(1L in set)
+        assertTrue(2L in set)
+        assertFalse(5L in set)
+    }
+
+    @Test
+    fun longSetOfThree() {
+        val set = longSetOf(1L, 2L, 3L)
+        assertEquals(3, set.size)
+        assertTrue(1L in set)
+        assertTrue(2L in set)
+        assertTrue(3L in set)
+        assertFalse(5L in set)
+    }
+
+    @Test
+    fun longSetOfFour() {
+        val set = longSetOf(1L, 2L, 3L, 4L)
+        assertEquals(4, set.size)
+        assertTrue(1L in set)
+        assertTrue(2L in set)
+        assertTrue(3L in set)
+        assertTrue(4L in set)
+        assertFalse(5L in set)
+    }
+
+    @Test
+    fun mutableLongSetOfOne() {
+        val set = mutableLongSetOf(1L)
+        assertEquals(1, set.size)
+        assertEquals(1L, set.first())
+    }
+
+    @Test
+    fun mutableLongSetOfTwo() {
+        val set = mutableLongSetOf(1L, 2L)
+        assertEquals(2, set.size)
+        assertTrue(1L in set)
+        assertTrue(2L in set)
+        assertFalse(5L in set)
+    }
+
+    @Test
+    fun mutableLongSetOfThree() {
+        val set = mutableLongSetOf(1L, 2L, 3L)
+        assertEquals(3, set.size)
+        assertTrue(1L in set)
+        assertTrue(2L in set)
+        assertTrue(3L in set)
+        assertFalse(5L in set)
+    }
+
+    @Test
+    fun mutableLongSetOfFour() {
+        val set = mutableLongSetOf(1L, 2L, 3L, 4L)
+        assertEquals(4, set.size)
+        assertTrue(1L in set)
+        assertTrue(2L in set)
+        assertTrue(3L in set)
+        assertTrue(4L in set)
+        assertFalse(5L in set)
+    }
 }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
index 201b2ef..60de883 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
@@ -21,6 +21,7 @@
 import kotlin.test.assertFalse
 import kotlin.test.assertNotEquals
 import kotlin.test.assertNull
+import kotlin.test.assertSame
 import kotlin.test.assertTrue
 
 class ScatterSetTest {
@@ -648,4 +649,84 @@
         assertTrue(set.trim() > 0)
         assertEquals(capacity, set.capacity)
     }
+
+    @Test
+    fun scatterSetOfEmpty() {
+        assertSame(emptyScatterSet(), scatterSetOf())
+        assertEquals(0, scatterSetOf().size)
+    }
+
+    @Test
+    fun scatterSetOfOne() {
+        val set = scatterSetOf("Hello")
+        assertEquals(1, set.size)
+        assertEquals("Hello", set.first())
+    }
+
+    @Test
+    fun scatterSetOfTwo() {
+        val set = scatterSetOf("Hello", "World")
+        assertEquals(2, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun scatterSetOfThree() {
+        val set = scatterSetOf("Hello", "World", "Hola")
+        assertEquals(3, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertTrue("Hola" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun scatterSetOfFour() {
+        val set = scatterSetOf("Hello", "World", "Hola", "Mundo")
+        assertEquals(4, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertTrue("Hola" in set)
+        assertTrue("Mundo" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun mutableScatterSetOfOne() {
+        val set = mutableScatterSetOf("Hello")
+        assertEquals(1, set.size)
+        assertEquals("Hello", set.first())
+    }
+
+    @Test
+    fun mutableScatterSetOfTwo() {
+        val set = mutableScatterSetOf("Hello", "World")
+        assertEquals(2, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun mutableScatterSetOfThree() {
+        val set = mutableScatterSetOf("Hello", "World", "Hola")
+        assertEquals(3, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertTrue("Hola" in set)
+        assertFalse("Bonjour" in set)
+    }
+
+    @Test
+    fun mutableScatterSetOfFour() {
+        val set = mutableScatterSetOf("Hello", "World", "Hola", "Mundo")
+        assertEquals(4, set.size)
+        assertTrue("Hello" in set)
+        assertTrue("World" in set)
+        assertTrue("Hola" in set)
+        assertTrue("Mundo" in set)
+        assertFalse("Bonjour" in set)
+    }
 }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
index 6ae0c2a..36e5ddc 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonTest.kt
@@ -17,6 +17,8 @@
 package androidx.compose.material
 
 import android.os.Build
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
@@ -26,8 +28,13 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.LayoutCoordinates
@@ -47,6 +54,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -148,14 +156,18 @@
         rule.setMaterialContent {
             Column {
                 Spacer(
-                    Modifier.requiredSize(10.dp).weight(1f).onGloballyPositioned {
-                        item1Bounds = it.boundsInRoot()
-                    }
+                    Modifier
+                        .requiredSize(10.dp)
+                        .weight(1f)
+                        .onGloballyPositioned {
+                            item1Bounds = it.boundsInRoot()
+                        }
                 )
 
                 FloatingActionButton(
                     onClick = {},
-                    modifier = Modifier.weight(1f)
+                    modifier = Modifier
+                        .weight(1f)
                         .onGloballyPositioned {
                             buttonBounds = it.boundsInRoot()
                         }
@@ -163,7 +175,10 @@
                     Text("Button")
                 }
 
-                Spacer(Modifier.requiredSize(10.dp).weight(1f))
+                Spacer(
+                    Modifier
+                        .requiredSize(10.dp)
+                        .weight(1f))
             }
         }
 
@@ -257,7 +272,8 @@
                     }
                 ) {
                     Box(
-                        Modifier.size(2.dp)
+                        Modifier
+                            .size(2.dp)
                             .onGloballyPositioned { contentCoordinates = it }
                     )
                 }
@@ -286,7 +302,8 @@
                 ExtendedFloatingActionButton(
                     text = {
                         Box(
-                            Modifier.size(2.dp)
+                            Modifier
+                                .size(2.dp)
                                 .onGloballyPositioned { contentCoordinates = it }
                         )
                     },
@@ -319,13 +336,15 @@
                 ExtendedFloatingActionButton(
                     text = {
                         Box(
-                            Modifier.size(2.dp)
+                            Modifier
+                                .size(2.dp)
                                 .onGloballyPositioned { textCoordinates = it }
                         )
                     },
                     icon = {
                         Box(
-                            Modifier.size(10.dp)
+                            Modifier
+                                .size(10.dp)
                                 .onGloballyPositioned { iconCoordinates = it }
                         )
                     },
@@ -355,4 +374,112 @@
             }
         }
     }
+
+    @Test
+    fun floatingActionButtonElevation_newInteraction() {
+        val interactionSource = MutableInteractionSource()
+        val defaultElevation = 1.dp
+        val pressedElevation = 2.dp
+        val hoveredElevation = 3.dp
+        val focusedElevation = 4.dp
+        lateinit var elevation: State
+
+        rule.setMaterialContent {
+            val fabElevation = FloatingActionButtonDefaults.elevation(
+                defaultElevation = defaultElevation,
+                pressedElevation = pressedElevation,
+                hoveredElevation = hoveredElevation,
+                focusedElevation = focusedElevation
+            )
+
+            elevation = fabElevation.elevation(interactionSource)
+        }
+
+        rule.runOnIdle {
+            assertThat(elevation.value).isEqualTo(defaultElevation)
+        }
+
+        rule.runOnIdle {
+            interactionSource.tryEmit(PressInteraction.Press(Offset.Zero))
+        }
+
+        rule.runOnIdle {
+            assertThat(elevation.value).isEqualTo(pressedElevation)
+        }
+    }
+
+    @Test
+    fun floatingActionButtonElevation_newValue() {
+        val interactionSource = MutableInteractionSource()
+        var defaultElevation by mutableStateOf(1.dp)
+        val pressedElevation = 2.dp
+        val hoveredElevation = 3.dp
+        val focusedElevation = 4.dp
+        lateinit var elevation: State
+
+        rule.setMaterialContent {
+             val fabElevation = FloatingActionButtonDefaults.elevation(
+                defaultElevation = defaultElevation,
+                pressedElevation = pressedElevation,
+                hoveredElevation = hoveredElevation,
+                focusedElevation = focusedElevation
+            )
+
+            elevation = fabElevation.elevation(interactionSource)
+        }
+
+        rule.runOnIdle {
+            assertThat(elevation.value).isEqualTo(defaultElevation)
+        }
+
+        rule.runOnIdle {
+            defaultElevation = 5.dp
+        }
+
+        rule.runOnIdle {
+            assertThat(elevation.value).isEqualTo(5.dp)
+        }
+    }
+
+    @Test
+    fun floatingActionButtonElevation_newValueDuringInteraction() {
+        val interactionSource = MutableInteractionSource()
+        val defaultElevation = 1.dp
+        var pressedElevation by mutableStateOf(2.dp)
+        val hoveredElevation = 3.dp
+        val focusedElevation = 4.dp
+        lateinit var elevation: State
+
+        rule.setMaterialContent {
+            val fabElevation = FloatingActionButtonDefaults.elevation(
+                defaultElevation = defaultElevation,
+                pressedElevation = pressedElevation,
+                hoveredElevation = hoveredElevation,
+                focusedElevation = focusedElevation
+            )
+
+            elevation = fabElevation.elevation(interactionSource)
+        }
+
+        rule.runOnIdle {
+            assertThat(elevation.value).isEqualTo(defaultElevation)
+        }
+
+        rule.runOnIdle {
+            interactionSource.tryEmit(PressInteraction.Press(Offset.Zero))
+        }
+
+        rule.runOnIdle {
+            assertThat(elevation.value).isEqualTo(pressedElevation)
+        }
+
+        rule.runOnIdle {
+            pressedElevation = 5.dp
+        }
+
+        // We are still pressed, so we should now show the updated value for the pressed state
+        rule.runOnIdle {
+            assertThat(elevation.value).isEqualTo(5.dp)
+        }
+    }
 }
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DatePickerBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DatePickerBenchmark.kt
index e5e5664..e5ba192 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DatePickerBenchmark.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DatePickerBenchmark.kt
@@ -27,6 +27,7 @@
 import androidx.compose.testutils.benchmark.benchmarkFirstCompose
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -42,40 +43,58 @@
     private val dateInputTestCaseFactory = { DateInputTestCase() }
 
     @Test
+    fun datePicker_firstPixel() {
+        benchmarkRule.benchmarkFirstRenderUntilStable(datePickerTestCaseFactory)
+    }
+
+    @Test
+    fun dateInput_firstPixel() {
+        benchmarkRule.benchmarkFirstRenderUntilStable(dateInputTestCaseFactory)
+    }
+
+    @Ignore
+    @Test
     fun first_compose_pickerMode() {
         benchmarkRule.benchmarkFirstCompose(datePickerTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun first_compose_inputMode() {
         benchmarkRule.benchmarkFirstCompose(dateInputTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun datePicker_measure() {
         benchmarkRule.benchmarkMeasureUntilStable(datePickerTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun dateInput_measure() {
         benchmarkRule.benchmarkMeasureUntilStable(dateInputTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun datePicker_layout() {
         benchmarkRule.benchmarkLayoutUntilStable(datePickerTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun dateInput_layout() {
         benchmarkRule.benchmarkLayoutUntilStable(dateInputTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun datePicker_draw() {
         benchmarkRule.benchmarkDrawUntilStable(datePickerTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun dateInput_draw() {
         benchmarkRule.benchmarkDrawUntilStable(dateInputTestCaseFactory)
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DateRangePickerBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DateRangePickerBenchmark.kt
index b9f97a3..61aec86 100644
--- a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DateRangePickerBenchmark.kt
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/DateRangePickerBenchmark.kt
@@ -28,8 +28,10 @@
 import androidx.compose.testutils.benchmark.benchmarkFirstDraw
 import androidx.compose.testutils.benchmark.benchmarkFirstLayout
 import androidx.compose.testutils.benchmark.benchmarkFirstMeasure
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,40 +47,58 @@
     private val dateRangeInputTestCaseFactory = { DateRangeInputTestCase() }
 
     @Test
+    fun dateRangePicker_firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel(dateRangePickerTestCaseFactory)
+    }
+
+    @Test
+    fun dateRangeInput_firstPixel() {
+        benchmarkRule.benchmarkFirstRenderUntilStable(dateRangeInputTestCaseFactory)
+    }
+
+    @Ignore
+    @Test
     fun first_compose_pickerMode() {
         benchmarkRule.benchmarkFirstCompose(dateRangePickerTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun first_compose_inputMode() {
         benchmarkRule.benchmarkFirstCompose(dateRangeInputTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun dateRangePicker_measure() {
         benchmarkRule.benchmarkFirstMeasure(dateRangePickerTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun dateRangeInput_measure() {
         benchmarkRule.benchmarkMeasureUntilStable(dateRangeInputTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun dateRangePicker_layout() {
         benchmarkRule.benchmarkFirstLayout(dateRangePickerTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun dateRangeInput_layout() {
         benchmarkRule.benchmarkLayoutUntilStable(dateRangeInputTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun dateRangePicker_draw() {
         benchmarkRule.benchmarkFirstDraw(dateRangePickerTestCaseFactory)
     }
 
+    @Ignore
     @Test
     fun dateRangeInput_draw() {
         benchmarkRule.benchmarkDrawUntilStable(dateRangeInputTestCaseFactory)
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
index 4d11904..9dfb619 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.material3
 
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
@@ -28,6 +30,7 @@
 import androidx.compose.material3.tokens.FabPrimarySmallTokens
 import androidx.compose.material3.tokens.FabPrimaryTokens
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -56,6 +59,7 @@
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -540,6 +544,127 @@
             .assertHeightIsEqualTo(FabPrimaryTokens.ContainerHeight)
             .assertWidthIsEqualTo(FabPrimaryTokens.ContainerWidth)
     }
+
+    @Test
+    fun floatingActionButtonElevation_newInteraction() {
+        val interactionSource = MutableInteractionSource()
+        val defaultElevation = 1.dp
+        val pressedElevation = 2.dp
+        val hoveredElevation = 3.dp
+        val focusedElevation = 4.dp
+        lateinit var tonalElevation: State
+        lateinit var shadowElevation: State
+
+        rule.setMaterialContent(lightColorScheme()) {
+            val fabElevation = FloatingActionButtonDefaults.elevation(
+                defaultElevation = defaultElevation,
+                pressedElevation = pressedElevation,
+                hoveredElevation = hoveredElevation,
+                focusedElevation = focusedElevation
+            )
+
+            tonalElevation = fabElevation.tonalElevation(interactionSource)
+            shadowElevation = fabElevation.shadowElevation(interactionSource)
+        }
+
+        rule.runOnIdle {
+            assertThat(tonalElevation.value).isEqualTo(defaultElevation)
+            assertThat(shadowElevation.value).isEqualTo(defaultElevation)
+        }
+
+        rule.runOnIdle {
+            interactionSource.tryEmit(PressInteraction.Press(Offset.Zero))
+        }
+
+        rule.runOnIdle {
+            assertThat(tonalElevation.value).isEqualTo(pressedElevation)
+            assertThat(shadowElevation.value).isEqualTo(pressedElevation)
+        }
+    }
+
+    @Test
+    fun floatingActionButtonElevation_newValue() {
+        val interactionSource = MutableInteractionSource()
+        var defaultElevation by mutableStateOf(1.dp)
+        val pressedElevation = 2.dp
+        val hoveredElevation = 3.dp
+        val focusedElevation = 4.dp
+        lateinit var tonalElevation: State
+        lateinit var shadowElevation: State
+
+        rule.setMaterialContent(lightColorScheme()) {
+            val fabElevation = FloatingActionButtonDefaults.elevation(
+                defaultElevation = defaultElevation,
+                pressedElevation = pressedElevation,
+                hoveredElevation = hoveredElevation,
+                focusedElevation = focusedElevation
+            )
+
+            tonalElevation = fabElevation.tonalElevation(interactionSource)
+            shadowElevation = fabElevation.shadowElevation(interactionSource)
+        }
+
+        rule.runOnIdle {
+            assertThat(tonalElevation.value).isEqualTo(defaultElevation)
+            assertThat(shadowElevation.value).isEqualTo(defaultElevation)
+        }
+
+        rule.runOnIdle {
+            defaultElevation = 5.dp
+        }
+
+        rule.runOnIdle {
+            assertThat(tonalElevation.value).isEqualTo(5.dp)
+            assertThat(shadowElevation.value).isEqualTo(5.dp)
+        }
+    }
+
+    @Test
+    fun floatingActionButtonElevation_newValueDuringInteraction() {
+        val interactionSource = MutableInteractionSource()
+        val defaultElevation = 1.dp
+        var pressedElevation by mutableStateOf(2.dp)
+        val hoveredElevation = 3.dp
+        val focusedElevation = 4.dp
+        lateinit var tonalElevation: State
+        lateinit var shadowElevation: State
+
+        rule.setMaterialContent(lightColorScheme()) {
+            val fabElevation = FloatingActionButtonDefaults.elevation(
+                defaultElevation = defaultElevation,
+                pressedElevation = pressedElevation,
+                hoveredElevation = hoveredElevation,
+                focusedElevation = focusedElevation
+            )
+
+            tonalElevation = fabElevation.tonalElevation(interactionSource)
+            shadowElevation = fabElevation.shadowElevation(interactionSource)
+        }
+
+        rule.runOnIdle {
+            assertThat(tonalElevation.value).isEqualTo(defaultElevation)
+            assertThat(shadowElevation.value).isEqualTo(defaultElevation)
+        }
+
+        rule.runOnIdle {
+            interactionSource.tryEmit(PressInteraction.Press(Offset.Zero))
+        }
+
+        rule.runOnIdle {
+            assertThat(tonalElevation.value).isEqualTo(pressedElevation)
+            assertThat(shadowElevation.value).isEqualTo(pressedElevation)
+        }
+
+        rule.runOnIdle {
+            pressedElevation = 5.dp
+        }
+
+        // We are still pressed, so we should now show the updated value for the pressed state
+        rule.runOnIdle {
+            assertThat(tonalElevation.value).isEqualTo(5.dp)
+            assertThat(shadowElevation.value).isEqualTo(5.dp)
+        }
+    }
 }
 
 fun assertWithinOnePixel(expected: Offset, actual: Offset) {
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/state/ComposeStateReadBenchmark.kt b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/state/ComposeStateReadBenchmark.kt
new file mode 100644
index 0000000..44a1a9f
--- /dev/null
+++ b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/state/ComposeStateReadBenchmark.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime.benchmark.state
+
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.compose.runtime.Applier
+import androidx.compose.runtime.Composition
+import androidx.compose.runtime.Recomposer
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.snapshots.SnapshotStateObserver
+import androidx.test.filters.LargeTest
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class ComposeStateReadBenchmark(private val readContext: ReadContext) {
+    enum class ReadContext {
+        Composition,
+        Measure;
+    }
+
+    companion object {
+        private const val MEASURE_OBSERVATION_DEPTH = 5
+        private val OnCommitInvalidatingMeasure: (Any) -> Unit = {}
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun parameters() = arrayOf(ReadContext.Composition, ReadContext.Measure)
+    }
+
+    @get:Rule
+    val benchmarkRule = BenchmarkRule()
+
+    @Test
+    fun readState() {
+        val state = mutableIntStateOf(0)
+
+        benchmarkRead {
+            state.value
+        }
+    }
+
+    @Test
+    fun readDerivedState() {
+        val stateA = mutableIntStateOf(0)
+        val stateB = mutableIntStateOf(0)
+        val derivedState = derivedStateOf { stateA.value + stateB.value }
+
+        derivedState.value // precompute result
+
+        benchmarkRead {
+            derivedState.value
+        }
+    }
+
+    @Test
+    fun readDerivedState_secondRead() {
+        val stateA = mutableIntStateOf(0)
+        val stateB = mutableIntStateOf(0)
+        val derivedState = derivedStateOf { stateA.value + stateB.value }
+
+        derivedState.value // precompute result
+
+        benchmarkRead(before = { derivedState.value }) {
+            derivedState.value
+        }
+    }
+
+    @Test
+    fun readDerivedState_afterWrite() {
+        val stateA = mutableIntStateOf(0)
+        val stateB = mutableIntStateOf(0)
+        val derivedState = derivedStateOf { stateA.value + stateB.value }
+
+        derivedState.value // precompute result
+
+        benchmarkRead(before = { stateA.value += 1 }) {
+            derivedState.value
+        }
+    }
+
+    @Test
+    fun readState_afterWrite() {
+        val stateA = mutableIntStateOf(0)
+
+        benchmarkRead(before = { stateA.value += 1 }) {
+            stateA.value
+        }
+    }
+
+    @Test
+    fun readState_preinitialized() {
+        val stateA = mutableIntStateOf(0)
+        val stateB = mutableIntStateOf(0)
+
+        benchmarkRead(before = { stateA.value }) {
+            stateB.value
+        }
+    }
+
+    @Test
+    fun readDerivedState_preinitialized() {
+        val stateA = mutableIntStateOf(0)
+        val stateB = mutableIntStateOf(0)
+
+        val derivedStateA = derivedStateOf { stateA.value + stateB.value }
+        val derivedStateB = derivedStateOf { stateB.value + stateA.value }
+
+        benchmarkRead(before = { derivedStateA.value }) {
+            derivedStateB.value
+        }
+    }
+
+    private fun benchmarkRead(
+        before: () -> Unit = {},
+        after: () -> Unit = {},
+        measure: () -> Unit
+    ) {
+        val benchmarkState = benchmarkRule.getState()
+        benchmarkRule.measureRepeated {
+            benchmarkState.pauseTiming()
+            runInReadObservationScope {
+                before()
+                benchmarkState.resumeTiming()
+
+                measure()
+
+                benchmarkState.pauseTiming()
+                after()
+            }
+            benchmarkRule.getState().resumeTiming()
+        }
+    }
+
+    private fun runInReadObservationScope(scopeBlock: () -> Unit) {
+        when (readContext) {
+            ReadContext.Composition -> createComposition().setContent { scopeBlock() }
+            ReadContext.Measure -> {
+                SnapshotStateObserver { it() }.apply {
+                    val nodes = List(MEASURE_OBSERVATION_DEPTH) { Any() }
+                    start()
+                    recursiveObserve(nodes, nodes.size, scopeBlock)
+                    stop()
+                }
+            }
+        }
+    }
+
+    private fun SnapshotStateObserver.recursiveObserve(
+        nodes: List,
+        depth: Int,
+        block: () -> Unit
+    ) {
+        if (depth == 0) {
+            block()
+            return
+        }
+        observeReads(nodes[depth - 1], OnCommitInvalidatingMeasure) {
+            recursiveObserve(nodes, depth - 1, block)
+        }
+    }
+
+    private fun createComposition(
+        coroutineContext: CoroutineContext = EmptyCoroutineContext
+    ): Composition {
+        val applier = UnitApplier()
+        val recomposer = Recomposer(coroutineContext)
+        return Composition(applier, recomposer)
+    }
+
+    private class UnitApplier : Applier {
+        override val current: Unit = Unit
+        override fun clear() {}
+        override fun move(from: Int, to: Int, count: Int) {}
+        override fun remove(index: Int, count: Int) {}
+        override fun up() {}
+        override fun insertTopDown(index: Int, instance: Unit) {}
+        override fun insertBottomUp(index: Int, instance: Unit) {}
+        override fun down(node: Unit) {}
+    }
+}
diff --git a/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt b/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt
index e712b4a..13d9104 100644
--- a/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt
+++ b/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt
@@ -113,3 +113,5 @@
 internal actual fun logError(message: String, e: Throwable) {
     Log.e(LogTag, message, e)
 }
+
+internal actual val MainThreadId: Long = Looper.getMainLooper()?.thread?.id ?: -1
diff --git a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt
index 6594283..a150271 100644
--- a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt
+++ b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt
@@ -102,3 +102,5 @@
     System.err.println(message)
     e.printStackTrace(System.err)
 }
+
+internal actual val MainThreadId: Long = -1
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt
index e73fbb9..71576e4 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt
@@ -50,19 +50,34 @@
     private val map = AtomicReference(emptyThreadMap)
     private val writeMutex = Any()
 
+    private var mainThreadValue: T? = null
+
     @Suppress("UNCHECKED_CAST")
-    actual fun get(): T? = map.get().get(Thread.currentThread().id) as T?
+    actual fun get(): T? {
+        val threadId = Thread.currentThread().id
+        return if (threadId == MainThreadId) {
+            mainThreadValue
+        } else {
+            map.get().get(Thread.currentThread().id) as T?
+        }
+    }
 
     actual fun set(value: T?) {
         val key = Thread.currentThread().id
-        synchronized(writeMutex) {
-            val current = map.get()
-            if (current.trySet(key, value)) return
-            map.set(current.newWith(key, value))
+        if (key == MainThreadId) {
+            mainThreadValue = value
+        } else {
+            synchronized(writeMutex) {
+                val current = map.get()
+                if (current.trySet(key, value)) return
+                map.set(current.newWith(key, value))
+            }
         }
     }
 }
 
+internal expect val MainThreadId: Long
+
 internal actual fun identityHashCode(instance: Any?): Int = System.identityHashCode(instance)
 
 @PublishedApi
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/drawscope/DrawScopeTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/drawscope/DrawScopeTest.kt
index 55a81a0..7f26707 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/drawscope/DrawScopeTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/drawscope/DrawScopeTest.kt
@@ -23,8 +23,10 @@
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.ClipOp
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.FilterQuality
 import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.ImageBitmapConfig
 import androidx.compose.ui.graphics.LinearGradientShader
 import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.PaintingStyle
@@ -40,6 +42,7 @@
 import androidx.compose.ui.graphics.nativeCanvas
 import androidx.compose.ui.graphics.toPixelMap
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -1770,6 +1773,52 @@
         }
     }
 
+    @Test
+    fun testBrushResetOnSubsequentDrawWithAlphaBitmap() {
+        val width = 200
+        val height = 200
+        val brush = Brush.horizontalGradient(
+            listOf(Color.Transparent, Color.Blue, Color.Transparent)
+        )
+        val maskBitmap = ImageBitmap(width / 2, height / 2, ImageBitmapConfig.Alpha8)
+        val maskCanvas = Canvas(maskBitmap)
+        maskCanvas.drawRect(
+            Rect(0f, 0f, width.toFloat(), height.toFloat()),
+            Paint().apply { color = Color.Green }
+        )
+        val colorFilter = ColorFilter.tint(Color.Red)
+        testDrawScopeAndCanvasAreEquivalent(
+            width,
+            height,
+            {
+                // Drawing an ImageBitmap after drawing a brush should unset the
+                // previously configured brush
+                drawRect(brush)
+                inset(width / 4f, height / 4f) {
+                    drawImage(maskBitmap, colorFilter = colorFilter)
+                }
+            },
+            { canvas ->
+                val paint = Paint().apply {
+                    brush.applyTo(Size(width.toFloat(), height.toFloat()), this, 1f)
+                }
+                canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
+                canvas.save()
+                canvas.translate(width / 4f, height / 4f)
+                canvas.drawImageRect(
+                    maskBitmap,
+                    srcOffset = IntOffset.Zero,
+                    srcSize = IntSize(width, height),
+                    dstOffset = IntOffset.Zero,
+                    dstSize = IntSize(width, height),
+                    Paint().apply {
+                        this.colorFilter = colorFilter
+                    })
+                canvas.restore()
+            }
+        )
+    }
+
     private inline fun testDrawTransformDefault(block: WrappedDrawTransform.() -> Unit) {
         val width = 100
         val height = 150
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
index 36fe832..c8ea35b 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
@@ -614,8 +614,10 @@
     ): Paint = selectPaint(style).apply {
         if (brush != null) {
             brush.applyTo(size, this, alpha)
-        } else if (this.alpha != alpha) {
-            this.alpha = alpha
+        } else {
+            if (this.shader != null) this.shader = null
+            if (this.color != Color.Black) this.color = Color.Black
+            if (this.alpha != alpha) this.alpha = alpha
         }
         if (this.colorFilter != colorFilter) this.colorFilter = colorFilter
         if (this.blendMode != blendMode) this.blendMode = blendMode
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index dda58d9..48e4a35 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -1967,9 +1967,11 @@
 
   public final class RotaryScrollEvent {
     method public float getHorizontalScrollPixels();
+    method public int getInputDeviceId();
     method public long getUptimeMillis();
     method public float getVerticalScrollPixels();
     property public final float horizontalScrollPixels;
+    property public final int inputDeviceId;
     property public final long uptimeMillis;
     property public final float verticalScrollPixels;
   }
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 1557073..e254bab 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -1967,9 +1967,11 @@
 
   public final class RotaryScrollEvent {
     method public float getHorizontalScrollPixels();
+    method public int getInputDeviceId();
     method public long getUptimeMillis();
     method public float getVerticalScrollPixels();
     property public final float horizontalScrollPixels;
+    property public final int inputDeviceId;
     property public final long uptimeMillis;
     property public final float verticalScrollPixels;
   }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
index 3a813aa..0e0a75b 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
@@ -26,6 +26,7 @@
 import androidx.activity.ComponentActivity
 import androidx.annotation.RequiresApi
 import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.height
@@ -863,7 +864,11 @@
                 contentDescription = null,
                 modifier = Modifier
                     .testTag(testTag)
-                    .background(Color.Red)
+                    .background(
+                        Brush.horizontalGradient(
+                            listOf(Color.Transparent, Color.Yellow, Color.Transparent)
+                        )
+                    )
                     .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen },
                 contentScale = ContentScale.FillBounds,
                 colorFilter = colorFilter
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
index 6d7da47..e350b81 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
@@ -64,7 +64,7 @@
 
     private val sentEvent: Any = when (nodeType) {
         KeyInput, InterruptedSoftKeyboardInput -> KeyEvent(AndroidKeyEvent(ACTION_DOWN, KEYCODE_A))
-        RotaryInput -> RotaryScrollEvent(1f, 1f, 0L)
+        RotaryInput -> RotaryScrollEvent(1f, 1f, 0L, 0)
     }
     private var receivedEvent: Any? = null
     private val initialFocus = FocusRequester()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
index c2b3768..31db4be 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
@@ -18,8 +18,11 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReusableContent
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -858,4 +861,45 @@
         }
         assertThat(events).hasSize(2)
     }
+
+    @Test
+    @MediumTest
+    fun lambdaIsRecapturedWhenReused() {
+        val tag = "box"
+        val events = mutableListOf()
+
+        @Composable
+        fun BoxWithKey(key: Int) {
+            // imitating one of the recommended patterns for Modifier.pointerInput() where we use
+            // rememberUpdatedState in order to have the latest value inside the suspending lambda.
+            // technically the state backing rememberUpdatedState will be recreated when the reuse
+            // happens so Modifier.pointerInput() have to update it's lambda to the new one even
+            // given that the key (Unit) didn't change.
+            val currentKey by rememberUpdatedState(key)
+            Box(
+                Modifier
+                    .testTag(tag)
+                    .fillMaxSize()
+                    .pointerInput(Unit) {
+                        events.add(currentKey)
+                    })
+        }
+
+        var key by mutableStateOf(0)
+
+        rule.setContent {
+            ReusableContent(key = key) {
+                BoxWithKey(key)
+            }
+        }
+
+        rule.runOnIdle {
+            key++
+        }
+
+        rule.onNodeWithTag(tag).performTouchInput {
+            down(Offset.Zero)
+        }
+        assertThat(events).isEqualTo(listOf(key))
+    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEventTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEventTest.kt
index 06d53a3..e6eb02c 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEventTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEventTest.kt
@@ -274,6 +274,41 @@
     }
 
     @Test
+    fun rotaryEventHasDeviceId() {
+        val DEVICE_ID = 1234
+
+        // Arrange.
+        ContentWithInitialFocus {
+            Box(
+                modifier = Modifier
+                    .onRotaryScrollEvent {
+                        receivedEvent = it
+                        true
+                    }
+                    .focusable(initiallyFocused = true)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            rootView.dispatchGenericMotionEvent(
+                MotionEventBuilder.newBuilder()
+                    .setAction(ACTION_SCROLL)
+                    .setSource(SOURCE_ROTARY_ENCODER)
+                    .setDeviceId(DEVICE_ID)
+                    .build()
+            )
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            with(checkNotNull(receivedEvent)) {
+                assertThat(inputDeviceId).isEqualTo(DEVICE_ID)
+            }
+        }
+    }
+
+    @Test
     fun rotaryEventUsesTestTime() {
         val TIME_DELTA = 1234L
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
index 205b312..44ae78e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
@@ -2382,7 +2382,10 @@
                 val content = if (showContent) {
                     subcompose(0) {
                         Box {
-                            AndroidView(::View, Modifier.fillMaxSize().testTag("AndroidView"))
+                            AndroidView(::View,
+                                Modifier
+                                    .fillMaxSize()
+                                    .testTag("AndroidView"))
                         }
                     }
                 } else emptyList()
@@ -2495,7 +2498,9 @@
                         Box {
                             SubcomposeLayout { constraints ->
                                 val placeable = measure(Unit, constraints) {
-                                    Box(modifier = Modifier.size(10.dp).then(measureCountModifier))
+                                    Box(modifier = Modifier
+                                        .size(10.dp)
+                                        .then(measureCountModifier))
 
                                     DisposableEffect(Unit) {
                                         val capturedSlotId = slotId
@@ -2539,6 +2544,42 @@
         }
     }
 
+    @Test
+    fun slotIsProperlyDeactivatedAfterUpdatingReusePolicy() {
+        var state by mutableStateOf(SubcomposeLayoutState(SubcomposeSlotReusePolicy(1)))
+        var shouldCompose by mutableStateOf(true)
+        var disposed = false
+        rule.setContent {
+            SubcomposeLayout(state) { constraints ->
+                val placeables = if (shouldCompose) {
+                    subcompose(Unit) {
+                        DisposableEffect(Unit) {
+                            onDispose {
+                                disposed = true
+                            }
+                        }
+                    }.map {
+                        it.measure(constraints)
+                    }
+                } else {
+                    emptyList()
+                }
+                layout(100, 100) {
+                    placeables.forEach { it.place(0, 0) }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            state = SubcomposeLayoutState(SubcomposeSlotReusePolicy(1))
+            shouldCompose = false
+        }
+
+        rule.runOnIdle {
+            assertThat(disposed).isTrue()
+        }
+    }
+
     private fun SubcomposeMeasureScope.measure(
         slotId: Any,
         constraints: Constraints,
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.android.kt
new file mode 100644
index 0000000..8c3e583
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.android.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.input.rotary
+
+/**
+ * This event represents a rotary input event.
+ *
+ * Some Wear OS devices contain a physical rotating side button, or a rotating bezel. When the user
+ * turns the button or rotates the bezel, a [RotaryScrollEvent] is sent to the item in focus.
+ */
+actual class RotaryScrollEvent internal constructor(
+    /**
+     * The amount to scroll (in pixels) in response to a [RotaryScrollEvent] in a container that
+     * can scroll vertically.
+     */
+    actual val verticalScrollPixels: Float,
+
+    /**
+     * The amount to scroll (in pixels) in response to a [RotaryScrollEvent] in a container that
+     * can scroll horizontally.
+     */
+    actual val horizontalScrollPixels: Float,
+
+    /**
+     * The time in milliseconds at which this even occurred. The start (`0`) time is
+     * platform-dependent.
+     */
+    actual val uptimeMillis: Long,
+
+    /**
+     * The id for the input device that this event came from
+     */
+    val inputDeviceId: Int
+) {
+    override fun equals(other: Any?): Boolean = other is RotaryScrollEvent &&
+        other.verticalScrollPixels == verticalScrollPixels &&
+        other.horizontalScrollPixels == horizontalScrollPixels &&
+        other.uptimeMillis == uptimeMillis &&
+        other.inputDeviceId == inputDeviceId
+
+    override fun hashCode(): Int = 0
+        .let { verticalScrollPixels.hashCode() }
+        .let { 31 * it + horizontalScrollPixels.hashCode() }
+        .let { 31 * it + uptimeMillis.hashCode() }
+        .let { 31 * it + inputDeviceId.hashCode() }
+
+    override fun toString(): String = "RotaryScrollEvent(" +
+        "verticalScrollPixels=$verticalScrollPixels," +
+        "horizontalScrollPixels=$horizontalScrollPixels," +
+        "uptimeMillis=$uptimeMillis," +
+        "deviceId=$inputDeviceId)"
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 2f3b345..9fe5321 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -1399,7 +1399,8 @@
         val rotaryEvent = RotaryScrollEvent(
             verticalScrollPixels = axisValue * getScaledVerticalScrollFactor(config, context),
             horizontalScrollPixels = axisValue * getScaledHorizontalScrollFactor(config, context),
-            uptimeMillis = event.eventTime
+            uptimeMillis = event.eventTime,
+            inputDeviceId = event.deviceId
         )
         return focusOwner.dispatchRotaryEvent(rotaryEvent)
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.kt
index c79101e..10ec62d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.kt
@@ -18,41 +18,23 @@
 
 /**
  * This event represents a rotary input event.
- *
- * Some Wear OS devices contain a physical rotating side button, or a rotating bezel. When the user
- * turns the button or rotates the bezel, a [RotaryScrollEvent] is sent to the item in focus.
  */
-class RotaryScrollEvent internal constructor(
+expect class RotaryScrollEvent {
     /**
      * The amount to scroll (in pixels) in response to a [RotaryScrollEvent] in a container that
      * can scroll vertically.
      */
-    val verticalScrollPixels: Float,
+    val verticalScrollPixels: Float
 
     /**
      * The amount to scroll (in pixels) in response to a [RotaryScrollEvent] in a container that
      * can scroll horizontally.
      */
-    val horizontalScrollPixels: Float,
+    val horizontalScrollPixels: Float
 
     /**
      * The time in milliseconds at which this even occurred. The start (`0`) time is
      * platform-dependent.
      */
     val uptimeMillis: Long
-) {
-    override fun equals(other: Any?): Boolean = other is RotaryScrollEvent &&
-        other.verticalScrollPixels == verticalScrollPixels &&
-        other.horizontalScrollPixels == horizontalScrollPixels &&
-        other.uptimeMillis == uptimeMillis
-
-    override fun hashCode(): Int = 0
-            .let { verticalScrollPixels.hashCode() }
-            .let { 31 * it + horizontalScrollPixels.hashCode() }
-            .let { 31 * it + uptimeMillis.hashCode() }
-
-    override fun toString(): String = "RotaryScrollEvent(" +
-        "verticalScrollPixels=$verticalScrollPixels," +
-        "horizontalScrollPixels=$horizontalScrollPixels," +
-        "uptimeMillis=$uptimeMillis)"
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index 5c52ed5..8abb20b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -567,15 +567,16 @@
                         node.resetLayoutState()
                         if (deactivate) {
                             nodeState.composition?.deactivate()
+                            nodeState.activeState = mutableStateOf(false)
+                        } else {
+                            nodeState.active = false
                         }
                         // create a new instance to avoid change notifications
-                        nodeState.activeState = mutableStateOf(false)
                         nodeState.slotId = ReusedSlotId
                     }
                 }
             }
             slotIdToNode.clear()
-            Snapshot.sendApplyNotifications()
         }
 
         makeSureStateIsConsistent()
@@ -673,7 +674,6 @@
             nodeState.activeState = mutableStateOf(true)
             nodeState.forceReuse = true
             nodeState.forceRecompose = true
-            Snapshot.sendApplyNotifications()
             node
         }
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
index 20e43e3..b205277 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.areObjectsOfSameType
+import androidx.compose.ui.input.pointer.SuspendPointerInputElement
 import androidx.compose.ui.layout.ModifierInfo
 
 private val SentinelHead = object : Modifier.Node() {
@@ -218,6 +219,16 @@
         tailToHead {
             if (it.isAttached) it.reset()
         }
+        current?.let { elements ->
+            elements.forEachIndexed { i, element ->
+                // we need to make sure the suspending pointer input modifier node is updated after
+                // being reset so we use the latest lambda, even if the keys provided as input
+                // didn't change.
+                if (element is SuspendPointerInputElement) {
+                    elements[i] = ForceUpdateElement
+                }
+            }
+        }
         runDetachLifecycle()
         markAsDetached()
     }
@@ -797,7 +808,7 @@
 internal fun actionForModifiers(prev: Modifier.Element, next: Modifier.Element): Int {
     return if (prev == next)
         ActionReuse
-    else if (areObjectsOfSameType(prev, next))
+    else if (areObjectsOfSameType(prev, next) || prev === ForceUpdateElement)
         ActionUpdate
     else
         ActionReplace
@@ -833,3 +844,20 @@
     }
     return result
 }
+
+@Suppress("ModifierNodeInspectableProperties")
+private object ForceUpdateElement : ModifierNodeElement() {
+    override fun create(): Modifier.Node {
+        throw IllegalStateException("Shouldn't be called")
+    }
+
+    override fun update(node: Modifier.Node) {
+        throw IllegalStateException("Shouldn't be called")
+    }
+
+    override fun hashCode(): Int = 100
+
+    override fun equals(other: Any?): Boolean = other === this
+
+    override fun toString() = "ForceUpdateElement"
+}
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.desktop.kt
new file mode 100644
index 0000000..02ae758
--- /dev/null
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/rotary/RotaryScrollEvent.desktop.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.input.rotary
+
+/**
+ * This event represents a rotary input event.
+ *
+ * Some devices contain a physical rotating side button, or a rotating bezel. When the user
+ * turns the button or rotates the bezel, a [RotaryScrollEvent] is sent to the item in focus.
+ */
+actual class RotaryScrollEvent internal constructor(
+    /**
+     * The amount to scroll (in pixels) in response to a [RotaryScrollEvent] in a container that
+     * can scroll vertically.
+     */
+    actual val verticalScrollPixels: Float,
+
+    /**
+     * The amount to scroll (in pixels) in response to a [RotaryScrollEvent] in a container that
+     * can scroll horizontally.
+     */
+    actual val horizontalScrollPixels: Float,
+
+    /**
+     * The time in milliseconds at which this even occurred. The start (`0`) time is
+     * platform-dependent.
+     */
+    actual val uptimeMillis: Long,
+
+) {
+    override fun equals(other: Any?): Boolean = other is RotaryScrollEvent &&
+        other.verticalScrollPixels == verticalScrollPixels &&
+        other.horizontalScrollPixels == horizontalScrollPixels &&
+        other.uptimeMillis == uptimeMillis
+
+    override fun hashCode(): Int = 0
+        .let { verticalScrollPixels.hashCode() }
+        .let { 31 * it + horizontalScrollPixels.hashCode() }
+        .let { 31 * it + uptimeMillis.hashCode() }
+
+    override fun toString(): String = "RotaryScrollEvent(" +
+        "verticalScrollPixels=$verticalScrollPixels," +
+        "horizontalScrollPixels=$horizontalScrollPixels," +
+        "uptimeMillis=$uptimeMillis)"
+}
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index e0af7f0..f56473d 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -720,13 +720,3 @@
 w: file://\$SUPPORT/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTestUtils\.kt:[0-9]+:[0-9]+ 'getter for windowLayoutParams: EspressoOptional!' is deprecated\. Deprecated in Java
 # b/271306193 remove after aosp/2589888 :emoji:emoji:spdxSbomForRelease
 spdx sboms require a version but project: noto\-emoji\-compat\-flatbuffers has no specified version
-# > Configure project :androidx-demos
-WARNING: The option setting 'android\.experimental\.disableCompileSdkChecks=true' is experimental\.
-The current default is 'false'\.
-WARNING: The option setting 'android\.r[0-9]+\.maxWorkers=[0-9]+' is experimental\.
-# > Task :compose:ui:ui:compileReleaseKotlinAndroid
-e: Daemon compilation failed: Could not connect to Kotlin compile daemon
-java\.lang\.RuntimeException: Could not connect to Kotlin compile daemon
-Errors were stored into \$SUPPORT/\.gradle/kotlin/errors/errors\-[0-9]+\.log
-# > Task :vectordrawable:integration-tests:testapp:createReleaseCompatibleScreenManifests
-exception: info: \[ksp\] loaded provider\(s\): \[androidx\.room\.RoomKspProcessor\$Provider\]
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index b16457b..ed38f46 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -29,7 +29,7 @@
     api("androidx.collection:collection:1.1.0")
     api("androidx.viewpager:viewpager:1.0.0")
     api("androidx.loader:loader:1.0.0")
-    api(project(":activity:activity"))
+    api(projectOrArtifact(":activity:activity"))
     api("androidx.lifecycle:lifecycle-runtime:2.6.1")
     api("androidx.lifecycle:lifecycle-livedata-core:2.6.1")
     api("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLFrameBufferRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLFrameBufferRendererTest.kt
index 1d69de6..ac06484 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLFrameBufferRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLFrameBufferRendererTest.kt
@@ -431,18 +431,27 @@
                 renderLatch.countDown()
             }
         }
+        var activity: SurfaceViewTestActivity? = null
         var renderer: GLFrameBufferRenderer? = null
         var surfaceView: SurfaceView?
         try {
             val scenario = ActivityScenario.launch(SurfaceViewTestActivity::class.java)
                 .moveToState(Lifecycle.State.CREATED)
                 .onActivity {
+                    activity = it
                     surfaceView = it.getSurfaceView()
                     renderer = GLFrameBufferRenderer.Builder(surfaceView!!, callbacks).build()
                 }
 
             scenario.moveToState(Lifecycle.State.RESUMED)
             assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
+
+            val destroyLatch = CountDownLatch(1)
+            activity?.setOnDestroyCallback {
+                destroyLatch.countDown()
+            }
+            scenario.moveToState(Lifecycle.State.DESTROYED)
+            assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
         } finally {
             renderer.blockingRelease()
         }
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SurfaceViewTestActivity.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SurfaceViewTestActivity.kt
index d21b990..831cc8b 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SurfaceViewTestActivity.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SurfaceViewTestActivity.kt
@@ -36,6 +36,8 @@
         setContentView(surfaceView, ViewGroup.LayoutParams(WIDTH, HEIGHT))
     }
 
+    private var mOnDestroyCallback: (() -> Unit)? = null
+
     fun getSurfaceView(): TestSurfaceView = mSurfaceView
 
     companion object {
@@ -76,4 +78,13 @@
             }
         }
     }
+
+    fun setOnDestroyCallback(callback: (() -> Unit)?) {
+        mOnDestroyCallback = callback
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        mOnDestroyCallback?.invoke()
+    }
 }
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContractTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContractTest.kt
index 0c7cdb5d..370aadb 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContractTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContractTest.kt
@@ -27,6 +27,7 @@
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -38,8 +39,27 @@
     private val context: Context = ApplicationProvider.getApplicationContext()
 
     @Test
+    fun createIntent_nonHealthPermission_throwsIAE() {
+        val requestPermissionContract = HealthPermissionsRequestContract()
+        assertFailsWith {
+            requestPermissionContract.createIntent(
+                context,
+                setOf(HealthPermission.READ_STEPS, "NON_HEALTH_PERMISSION")
+            )
+        }
+    }
+
+    @Test
+    fun createIntent_noPermissions_throwsIAE() {
+        val requestPermissionContract = HealthPermissionsRequestContract()
+        assertFailsWith {
+            requestPermissionContract.createIntent(context, setOf())
+        }
+    }
+
+    @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    fun requestHealthPermissions_createIntent_hasPlatformIntentAction() {
+    fun createIntent_hasPlatformIntentAction() {
         val intent =
             HealthPermissionsRequestContract()
                 .createIntent(context, setOf(HealthPermission.READ_STEPS))
@@ -51,7 +71,7 @@
 
     @Test
     @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.TIRAMISU)
-    fun requestHealthPermissions_createIntent_hasApkIntentAction() {
+    fun createIntent_hasApkIntentAction() {
         val intent =
             HealthPermissionsRequestContract()
                 .createIntent(context, setOf(HealthPermission.READ_STEPS))
@@ -61,7 +81,7 @@
 
     @Test
     @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.TIRAMISU)
-    fun requestHealthPermissions_createIntent_hasApkIntentActionAndProvider() {
+    fun createIntent_hasApkIntentActionAndProvider() {
         val intent =
             HealthPermissionsRequestContract("some.provider")
                 .createIntent(context, setOf(HealthPermission.READ_STEPS))
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt
index 1251304..679412f 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt
@@ -22,6 +22,7 @@
 import androidx.activity.result.contract.ActivityResultContract
 import androidx.health.connect.client.HealthConnectClient
 import androidx.health.connect.client.permission.HealthDataRequestPermissionsInternal
+import androidx.health.connect.client.permission.HealthPermission
 import androidx.health.connect.client.permission.platform.HealthDataRequestPermissionsUpsideDownCake
 
 /**
@@ -50,6 +51,10 @@
      * @see ActivityResultContract.createIntent
      */
     override fun createIntent(context: Context, input: Set): Intent {
+        require(input.all { it.startsWith(HealthPermission.PERMISSION_PREFIX) }) {
+            "Unsupported health connect permission"
+        }
+        require(input.isNotEmpty()) { "At least one permission is required!" }
         return delegate.createIntent(context, input)
     }
 
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt
index 28c1ee5..6ddad08 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt
@@ -32,8 +32,7 @@
  * An [ActivityResultContract] to request Health Connect permissions.
  *
  * @param providerPackageName Optional provider package name for the backing implementation of
- * choice.
- *
+ *   choice.
  * @see androidx.activity.ComponentActivity.registerForActivityResult
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -42,8 +41,6 @@
 ) : ActivityResultContract, Set>() {
 
     override fun createIntent(context: Context, input: Set): Intent {
-        require(input.isNotEmpty()) { "At least one permission is required!" }
-
         val protoPermissionList =
             input
                 .asSequence()
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCake.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCake.kt
index e6994c5..e4dd720 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCake.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCake.kt
@@ -20,7 +20,6 @@
 import androidx.activity.result.contract.ActivityResultContract
 import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
 import androidx.annotation.RestrictTo
-import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_PREFIX
 
 /**
  * An [ActivityResultContract] to request Health Connect system permissions.
@@ -33,12 +32,8 @@
 
     private val requestPermissions = RequestMultiplePermissions()
 
-    override fun createIntent(context: Context, input: Set): Intent {
-        require(input.all { it.startsWith(PERMISSION_PREFIX) }) {
-            "Unsupported health connect permission"
-        }
-        return requestPermissions.createIntent(context, input.toTypedArray())
-    }
+    override fun createIntent(context: Context, input: Set): Intent =
+        requestPermissions.createIntent(context, input.toTypedArray())
 
     override fun parseResult(resultCode: Int, intent: Intent?): Set =
         requestPermissions.parseResult(resultCode, intent).filterValues { it }.keys
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCakeTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCakeTest.kt
index 2bbd4f4..f284bd6a 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCakeTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCakeTest.kt
@@ -25,7 +25,6 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFailsWith
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,7 +44,9 @@
         val requestPermissionContract = HealthDataRequestPermissionsUpsideDownCake()
         val intent =
             requestPermissionContract.createIntent(
-                context, setOf(HealthPermission.READ_STEPS, HealthPermission.WRITE_DISTANCE))
+                context,
+                setOf(HealthPermission.READ_STEPS, HealthPermission.WRITE_DISTANCE)
+            )
 
         assertThat(intent.action).isEqualTo(RequestMultiplePermissions.ACTION_REQUEST_PERMISSIONS)
         assertThat(intent.getStringArrayExtra(RequestMultiplePermissions.EXTRA_PERMISSIONS))
@@ -54,15 +55,6 @@
     }
 
     @Test
-    fun createIntent_nonHealthPermission_throwsIAE() {
-        val requestPermissionContract = HealthDataRequestPermissionsUpsideDownCake()
-        assertFailsWith {
-            requestPermissionContract.createIntent(
-                context, setOf(HealthPermission.READ_STEPS, "NON_HEALTH_PERMISSION"))
-        }
-    }
-
-    @Test
     fun parseIntent() {
         val requestPermissionContract = HealthDataRequestPermissionsUpsideDownCake()
 
@@ -73,14 +65,18 @@
                 HealthPermission.READ_STEPS,
                 HealthPermission.WRITE_STEPS,
                 HealthPermission.WRITE_DISTANCE,
-                HealthPermission.READ_HEART_RATE))
+                HealthPermission.READ_HEART_RATE
+            )
+        )
         intent.putExtra(
             RequestMultiplePermissions.EXTRA_PERMISSION_GRANT_RESULTS,
             intArrayOf(
                 PackageManager.PERMISSION_GRANTED,
                 PackageManager.PERMISSION_DENIED,
                 PackageManager.PERMISSION_GRANTED,
-                PackageManager.PERMISSION_DENIED))
+                PackageManager.PERMISSION_DENIED
+            )
+        )
 
         val result = requestPermissionContract.parseResult(Activity.RESULT_OK, intent)
 
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanFilter.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanFilter.java
index 207b941..91f2d12 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanFilter.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanFilter.java
@@ -52,8 +52,16 @@
     // Measurement matrix
     public @NonNull Matrix H;
 
-    // Kalman gain
-    public @NonNull Matrix K;
+    // Buffers to minimize matrix allocations on every MotionEvent
+    private @NonNull Matrix mBufferXDimOne;
+    private @NonNull Matrix mBufferXDimXDim;
+    private @NonNull Matrix mBufferXDimXDim2;
+    private @NonNull Matrix mBufferXDimZDim;
+    private @NonNull Matrix mBufferXDimZDim2;
+    private @NonNull Matrix mBufferZDimOne;
+    private @NonNull Matrix mBufferZDimXDim;
+    private @NonNull Matrix mBufferZDimZDim;
+    private @NonNull Matrix mBufferZDimTwiceZDim;
 
     public KalmanFilter(int xDim, int zDim) {
         x = new Matrix(xDim, 1);
@@ -62,7 +70,15 @@
         R = Matrix.identity(zDim);
         F = new Matrix(xDim, xDim);
         H = new Matrix(zDim, xDim);
-        K = new Matrix(xDim, zDim);
+        mBufferXDimZDim = new Matrix(xDim, zDim);
+        mBufferXDimZDim2 = new Matrix(xDim, zDim);
+        mBufferXDimOne = new Matrix(xDim, 1);
+        mBufferXDimXDim = new Matrix(xDim, xDim);
+        mBufferXDimXDim2 = new Matrix(xDim, xDim);
+        mBufferZDimOne = new Matrix(zDim, 1);
+        mBufferZDimXDim = new Matrix(zDim, xDim);
+        mBufferZDimZDim = new Matrix(zDim, zDim);
+        mBufferZDimTwiceZDim = new Matrix(zDim, 2 * zDim);
     }
 
     /** Resets the internal state of this Kalman filter. */
@@ -70,7 +86,6 @@
         // NOTE: It is not necessary to reset Q, R, F, and H matrices.
         x.fill(0);
         Matrix.setIdentity(P);
-        K.fill(0);
     }
 
     /**
@@ -78,16 +93,24 @@
      * estimate for the current timestep.
      */
     public void predict() {
-        x = F.dot(x);
-        P = F.dot(P).dotTranspose(F).plus(Q);
+        Matrix originalX = x;
+        x = F.dot(x, mBufferXDimOne);
+        mBufferXDimOne = originalX;
+
+        F.dot(P, mBufferXDimXDim).dotTranspose(F, P).plus(Q);
     }
 
     /** Updates the state estimate to incorporate the new observation z. */
     public void update(@NonNull Matrix z) {
-        Matrix y = z.minus(H.dot(x));
-        Matrix tS = H.dot(P).dotTranspose(H).plus(R);
-        K = P.dotTranspose(H).dot(tS.inverse());
-        x = x.plus(K.dot(y));
-        P = P.minus(K.dot(H).dot(P));
+        z.minus(H.dot(x, mBufferZDimOne));
+        H.dot(P, mBufferZDimXDim)
+                .dotTranspose(H, mBufferZDimZDim)
+                .plus(R)
+                .inverse(mBufferZDimTwiceZDim);
+
+        P.dotTranspose(H, mBufferXDimZDim2).dot(mBufferZDimZDim, mBufferXDimZDim);
+
+        x.plus(mBufferXDimZDim.dot(z, mBufferXDimOne));
+        P.minus(mBufferXDimZDim.dot(H, mBufferXDimXDim).dot(P, mBufferXDimXDim2));
     }
 }
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/matrix/Matrix.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/matrix/Matrix.java
index 0294b18..399263d 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/matrix/Matrix.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/matrix/Matrix.java
@@ -230,27 +230,6 @@
      * Calculates the matrix product of this matrix and {@code that}.
      *
      * @param that the other matrix
-     * @return newly created matrix representing the matrix product of this and that
-     * @throws IllegalArgumentException if the dimensions differ
-     */
-    public @NonNull Matrix dot(@NonNull Matrix that) {
-        try {
-            return dot(that, new Matrix(mRows, that.mCols));
-        } catch (IllegalArgumentException e) {
-            throw new IllegalArgumentException(
-                    String.format(
-                            Locale.ROOT,
-                            "The matrices dimensions are not conformant for a dot matrix "
-                                    + "operation. this:%s that:%s",
-                            shortString(),
-                            that.shortString()));
-        }
-    }
-
-    /**
-     * Calculates the matrix product of this matrix and {@code that}.
-     *
-     * @param that the other matrix
      * @param result matrix to hold the result
      * @return result, filled with the matrix product
      * @throws IllegalArgumentException if the dimensions differ
@@ -281,15 +260,26 @@
     /**
      * Calculates the inverse of a square matrix
      *
+     * @param scratch the matrix [rows, 2*cols] to hold the temporary information
+     *
      * @return newly created matrix representing the matrix inverse
      * @throws ArithmeticException if the matrix is not invertible
      */
-    public @NonNull Matrix inverse() {
+    public @NonNull Matrix inverse(@NonNull Matrix scratch) {
         if (!(mRows == mCols)) {
             throw new IllegalArgumentException(
                     String.format(Locale.ROOT, "The matrix is not square. this:%s", shortString()));
         }
-        final Matrix scratch = new Matrix(mRows, 2 * mCols);
+
+        if (scratch.mRows != mRows || scratch.mCols != 2 * mCols) {
+            throw new IllegalArgumentException(
+                    String.format(
+                            Locale.ROOT,
+                            "The scratch matrix size is not correct. this:%s",
+                            scratch.shortString()
+                    )
+            );
+        }
 
         for (int i = 0; i < mRows; i++) {
             for (int j = 0; j < mCols; j++) {
@@ -349,27 +339,6 @@
      * Calculates the matrix product with the transpose of a second matrix.
      *
      * @param that the other matrix
-     * @return newly created matrix representing the matrix product of this and that.transpose()
-     * @throws IllegalArgumentException if shapes are not conformant
-     */
-    public @NonNull Matrix dotTranspose(@NonNull Matrix that) {
-        try {
-            return dotTranspose(that, new Matrix(mRows, that.mRows));
-        } catch (IllegalArgumentException e) {
-            throw new IllegalArgumentException(
-                    String.format(
-                            Locale.ROOT,
-                            "The matrices dimensions are not conformant for a transpose "
-                                    + "operation. this:%s that:%s",
-                            shortString(),
-                            that.shortString()));
-        }
-    }
-
-    /**
-     * Calculates the matrix product with the transpose of a second matrix.
-     *
-     * @param that the other matrix
      * @param result space to hold the result
      * @return result, filled with the matrix product of this and that.transpose()
      * @throws IllegalArgumentException if shapes are not conformant
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java
index 5ad04f2..02cb445 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java
@@ -360,8 +360,7 @@
 
     /* package */ void addMemberToDynamicGroup(@NonNull MediaRouter.RouteInfo route) {
         if (!(mSelectedRouteController instanceof MediaRouteProvider.DynamicGroupRouteController)) {
-            throw new IllegalStateException(
-                    "There is no currently selected " + "dynamic group route.");
+            throw new IllegalStateException("There is no currently selected dynamic group route.");
         }
         MediaRouter.RouteInfo.DynamicGroupState state = getDynamicGroupState(route);
         if (mSelectedRoute.getMemberRoutes().contains(route)
@@ -376,8 +375,7 @@
 
     /* package */ void removeMemberFromDynamicGroup(@NonNull MediaRouter.RouteInfo route) {
         if (!(mSelectedRouteController instanceof MediaRouteProvider.DynamicGroupRouteController)) {
-            throw new IllegalStateException(
-                    "There is no currently selected " + "dynamic group route.");
+            throw new IllegalStateException("There is no currently selected dynamic group route.");
         }
         MediaRouter.RouteInfo.DynamicGroupState state = getDynamicGroupState(route);
         if (!mSelectedRoute.getMemberRoutes().contains(route)
@@ -396,8 +394,7 @@
 
     /* package */ void transferToRoute(@NonNull MediaRouter.RouteInfo route) {
         if (!(mSelectedRouteController instanceof MediaRouteProvider.DynamicGroupRouteController)) {
-            throw new IllegalStateException(
-                    "There is no currently selected dynamic group " + "route.");
+            throw new IllegalStateException("There is no currently selected dynamic group route.");
         }
         MediaRouter.RouteInfo.DynamicGroupState state = getDynamicGroupState(route);
         if (state == null || !state.isTransferable()) {
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
index d000958..52c924c 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
@@ -21,6 +21,8 @@
 import androidx.fragment.app.DialogFragment
 import androidx.fragment.app.Fragment
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.navigation.NavOptions
@@ -35,7 +37,8 @@
 import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Ignore
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -75,7 +78,7 @@
             .that(navController.currentBackStackEntry!!.lifecycle.currentState)
             .isEqualTo(Lifecycle.State.RESUMED)
     }
-    @Ignore("b/276806142")
+
     @Test
     fun fragmentNavigateClearBackStack() = withNavigationActivity {
         navController.setGraph(R.navigation.nav_simple)
@@ -98,6 +101,14 @@
             TestClearViewModel::class.java
         ]
         val originalFragment = fm?.findFragmentById(R.id.nav_host) as Fragment
+        val destroyCountDownLatch = CountDownLatch(1)
+        originalFragment.lifecycle.addObserver(object : LifecycleEventObserver {
+            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+                if (event == Lifecycle.Event.ON_DESTROY) {
+                    destroyCountDownLatch.countDown()
+                }
+            }
+        })
         val originalFragmentViewModel = ViewModelProvider(originalFragment)[
             TestClearViewModel::class.java
         ]
@@ -125,6 +136,7 @@
         assertThat(fm.findFragmentById(R.id.nav_host)).isEqualTo(currentTopFragment)
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.empty_fragment_2)
         assertThat(navigator.backStack.value.size).isEqualTo(2)
+        assertThat(destroyCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
         assertThat(originalFragmentViewModel.cleared).isTrue()
         assertThat(originalEntryViewModel.cleared).isTrue()
     }
diff --git a/paging/paging-compose/src/androidAndroidTest/kotlin/androidx/paging/compose/LazyPagingItemsTest.kt b/paging/paging-compose/src/androidAndroidTest/kotlin/androidx/paging/compose/LazyPagingItemsTest.kt
index 76209f0..4399ddd 100644
--- a/paging/paging-compose/src/androidAndroidTest/kotlin/androidx/paging/compose/LazyPagingItemsTest.kt
+++ b/paging/paging-compose/src/androidAndroidTest/kotlin/androidx/paging/compose/LazyPagingItemsTest.kt
@@ -56,7 +56,6 @@
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -976,7 +975,6 @@
         }
     }
 
-    @Ignore // b/294941531
     @Test
     fun cachedData_loadStates() {
         val flow = createPager().flow.cachedIn(TestScope(UnconfinedTestDispatcher()))
@@ -994,7 +992,10 @@
 
         rule.waitUntil {
             dispatcher.scheduler.advanceUntilIdle() // let items load
-            lazyPagingItems.itemCount == maxItem
+            lazyPagingItems.itemCount == maxItem &&
+                lazyPagingItems.loadState.source.refresh is LoadState.NotLoading &&
+                lazyPagingItems.loadState.source.prepend is LoadState.NotLoading &&
+                lazyPagingItems.loadState.source.append is LoadState.NotLoading
         }
 
         assertThat(lazyPagingItems.loadState).isEqualTo(
diff --git a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
index c8953f1..ac7fe19 100644
--- a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
+++ b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
@@ -66,8 +66,8 @@
 import androidx.compose.ui.unit.dp
 import androidx.tv.material3.Carousel
 import androidx.tv.material3.CarouselDefaults
-import androidx.tv.material3.CarouselState
 import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.rememberCarouselState
 
 @Composable
 fun FeaturedCarouselContent() {
@@ -122,7 +122,7 @@
         Color.LightGray.copy(alpha = 0.3f),
     )
 
-    val carouselState = remember { CarouselState() }
+    val carouselState = rememberCarouselState()
     var carouselFocused by remember { mutableStateOf(false) }
     Carousel(
         itemCount = backgrounds.size,
@@ -141,9 +141,9 @@
             )
         },
         contentTransformStartToEnd =
-            fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))),
+        fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))),
         contentTransformEndToStart =
-            fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000)))
+        fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000)))
     ) { itemIndex ->
         Box(
             modifier = Modifier
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt
index e5fa951..2f07358 100644
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt
+++ b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt
@@ -33,7 +33,6 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.ArrowRight
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -44,6 +43,7 @@
 import androidx.tv.material3.CarouselState
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Text
+import androidx.tv.material3.rememberCarouselState
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
@@ -51,7 +51,7 @@
     movies: List = featuredCarouselMovies,
     modifier: Modifier = Modifier
 ) {
-    val carouselState: CarouselState = remember { CarouselState() }
+    val carouselState: CarouselState = rememberCarouselState()
     val slidesCount = movies.size
 
     Carousel(
diff --git a/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt b/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt
index 320ab30..c3ed957 100644
--- a/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt
+++ b/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt
@@ -52,8 +52,8 @@
 import androidx.compose.ui.unit.dp
 import androidx.tv.material3.Carousel
 import androidx.tv.material3.CarouselDefaults
-import androidx.tv.material3.CarouselState
 import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.rememberCarouselState
 
 @OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
 @Sampled
@@ -142,7 +142,7 @@
         Color.Yellow.copy(alpha = 0.3f),
         Color.Green.copy(alpha = 0.3f)
     )
-    val carouselState = remember { CarouselState() }
+    val carouselState = rememberCarouselState()
 
     Carousel(
         itemCount = backgrounds.size,
diff --git a/tv/tv-material/api/current.txt b/tv/tv-material/api/current.txt
index 3dca4bb..dabf56f 100644
--- a/tv/tv-material/api/current.txt
+++ b/tv/tv-material/api/current.txt
@@ -143,6 +143,7 @@
 
   public final class CarouselKt {
     method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Carousel(int itemCount, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.CarouselState carouselState, optional long autoScrollDurationMillis, optional androidx.compose.animation.ContentTransform contentTransformStartToEnd, optional androidx.compose.animation.ContentTransform contentTransformEndToStart, optional kotlin.jvm.functions.Function1 carouselIndicator, kotlin.jvm.functions.Function2 content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.CarouselState rememberCarouselState(optional int initialActiveItemIndex);
   }
 
   @SuppressCompatibility @androidx.compose.runtime.Stable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselState {
@@ -150,6 +151,12 @@
     method public int getActiveItemIndex();
     method public androidx.tv.material3.ScrollPauseHandle pauseAutoScroll(int itemIndex);
     property public final int activeItemIndex;
+    field public static final androidx.tv.material3.CarouselState.Companion Companion;
+  }
+
+  public static final class CarouselState.Companion {
+    method public androidx.compose.runtime.saveable.Saver getSaver();
+    property public final androidx.compose.runtime.saveable.Saver Saver;
   }
 
   @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CheckboxColors {
diff --git a/tv/tv-material/api/restricted_current.txt b/tv/tv-material/api/restricted_current.txt
index 3dca4bb..dabf56f 100644
--- a/tv/tv-material/api/restricted_current.txt
+++ b/tv/tv-material/api/restricted_current.txt
@@ -143,6 +143,7 @@
 
   public final class CarouselKt {
     method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Carousel(int itemCount, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.CarouselState carouselState, optional long autoScrollDurationMillis, optional androidx.compose.animation.ContentTransform contentTransformStartToEnd, optional androidx.compose.animation.ContentTransform contentTransformEndToStart, optional kotlin.jvm.functions.Function1 carouselIndicator, kotlin.jvm.functions.Function2 content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.CarouselState rememberCarouselState(optional int initialActiveItemIndex);
   }
 
   @SuppressCompatibility @androidx.compose.runtime.Stable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselState {
@@ -150,6 +151,12 @@
     method public int getActiveItemIndex();
     method public androidx.tv.material3.ScrollPauseHandle pauseAutoScroll(int itemIndex);
     property public final int activeItemIndex;
+    field public static final androidx.tv.material3.CarouselState.Companion Companion;
+  }
+
+  public static final class CarouselState.Companion {
+    method public androidx.compose.runtime.saveable.Saver getSaver();
+    property public final androidx.compose.runtime.saveable.Saver Saver;
   }
 
   @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CheckboxColors {
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
index 5538671..5b302d8 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
@@ -122,7 +122,7 @@
     @Test
     fun carousel_onUserTriggeredPause_stopsScroll() {
         rule.setContent {
-            val carouselState = remember { CarouselState() }
+            val carouselState = rememberCarouselState()
             SampleCarousel(carouselState = carouselState) {
                 BasicText(text = "Text ${it + 1}")
                 LaunchedEffect(carouselState) { carouselState.pauseAutoScroll(it) }
@@ -142,7 +142,7 @@
     fun carousel_onUserTriggeredPauseAndResume_resumeScroll() {
         var pauseHandle: ScrollPauseHandle? = null
         rule.setContent {
-            val carouselState = remember { CarouselState() }
+            val carouselState = rememberCarouselState()
             SampleCarousel(carouselState = carouselState) {
                 BasicText(text = "Text ${it + 1}")
                 LaunchedEffect(carouselState) {
@@ -176,7 +176,7 @@
         var pauseHandle1: ScrollPauseHandle? = null
         var pauseHandle2: ScrollPauseHandle? = null
         rule.setContent {
-            val carouselState = remember { CarouselState() }
+            val carouselState = rememberCarouselState()
             SampleCarousel(carouselState = carouselState) {
                 BasicText(text = "Text ${it + 1}")
                 LaunchedEffect(carouselState) {
@@ -219,7 +219,7 @@
     fun carousel_onRepeatedResumesOnSamePauseHandle_ignoresSubsequentResumeCalls() {
         var pauseHandle1: ScrollPauseHandle? = null
         rule.setContent {
-            val carouselState = remember { CarouselState() }
+            val carouselState = rememberCarouselState()
             var pauseHandle2: ScrollPauseHandle? = null
             SampleCarousel(carouselState = carouselState) {
                 BasicText(text = "Text ${it + 1}")
@@ -429,7 +429,7 @@
                             .fillMaxWidth()
                             .testTag("featured-carousel")
                             .border(2.dp, Color.Black),
-                        carouselState = remember { CarouselState() },
+                        carouselState = rememberCarouselState(),
                         itemCount = 3,
                         autoScrollDurationMillis = delayBetweenItems
                     ) {
@@ -833,7 +833,7 @@
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 private fun SampleCarousel(
-    carouselState: CarouselState = remember { CarouselState() },
+    carouselState: CarouselState = rememberCarouselState(),
     itemCount: Int = 3,
     timeToDisplayItemMillis: Long = delayBetweenItems,
     content: @Composable AnimatedContentScope.(index: Int) -> Unit
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
index 2da1c2d..23ffe4e 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
@@ -44,6 +44,8 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
@@ -107,7 +109,7 @@
 fun Carousel(
     itemCount: Int,
     modifier: Modifier = Modifier,
-    carouselState: CarouselState = remember { CarouselState() },
+    carouselState: CarouselState = rememberCarouselState(),
     autoScrollDurationMillis: Long = CarouselDefaults.TimeToDisplayItemMillis,
     contentTransformStartToEnd: ContentTransform = CarouselDefaults.contentTransform,
     contentTransformEndToStart: ContentTransform = CarouselDefaults.contentTransform,
@@ -260,6 +262,7 @@
         carouselState.moveToPreviousItem(itemCount)
         outerBoxFocusRequester.requestFocus()
     }
+
     fun showNextItem() {
         carouselState.moveToNextItem(itemCount)
         outerBoxFocusRequester.requestFocus()
@@ -293,6 +296,7 @@
                 updateItemBasedOnLayout(direction, isLtr)
                 KeyEventPropagation.StopPropagation
             }
+
             else -> KeyEventPropagation.StopPropagation
         }
 
@@ -315,6 +319,7 @@
         when {
             shouldFocusExitCarousel(it, carouselState, itemCount, isLtr) ->
                 FocusRequester.Default
+
             else -> FocusRequester.Cancel
         }
     }
@@ -350,6 +355,22 @@
 }
 
 /**
+ * Creates a [CarouselState] that is remembered across compositions.
+ *
+ * Changes to the provided initial values will **not** result in the state being recreated or
+ * changed in any way if it has already been created.
+ *
+ * @param initialActiveItemIndex the index of the first active item
+ */
+@ExperimentalTvMaterial3Api
+@Composable
+fun rememberCarouselState(initialActiveItemIndex: Int = 0): CarouselState {
+    return rememberSaveable(saver = CarouselState.Saver) {
+        CarouselState(initialActiveItemIndex)
+    }
+}
+
+/**
  * State of the Carousel which allows the user to specify the first item that is shown when the
  * Carousel is instantiated in the constructor.
  *
@@ -410,6 +431,16 @@
         // Go to next item
         activeItemIndex = floorMod(activeItemIndex + 1, itemCount)
     }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [CarouselState].
+         */
+        val Saver: Saver = Saver(
+            save = { it.activeItemIndex },
+            restore = { CarouselState(it) }
+        )
+    }
 }
 
 @ExperimentalTvMaterial3Api
diff --git a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/RepeatableClickable.kt b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/RepeatableClickable.kt
deleted file mode 100644
index 545a576..0000000
--- a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/RepeatableClickable.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.compose.materialcore
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performTouchInput
-import org.junit.Assert.assertEquals
-import org.junit.Rule
-import org.junit.Test
-
-public class RepeatableClickable {
-    @get:Rule
-    public val rule = createComposeRule()
-
-    @Test
-    fun touch_hold_shorter_than_threshold() {
-        var clickCounter = 0
-
-        boxWithRepeatableClickable(rule, 300) {
-            clickCounter++
-        }
-
-        assertEquals(0, clickCounter)
-    }
-
-    @Test
-    fun touch_hold_equals_to_threshold() {
-        var clickCounter = 0
-
-        boxWithRepeatableClickable(rule, 500) {
-            clickCounter++
-        }
-
-        assertEquals(1, clickCounter)
-    }
-
-    @Test
-    fun touch_hold_longer_than_threshold() {
-        var clickCounter = 0
-
-        boxWithRepeatableClickable(rule, 620) {
-            clickCounter++
-        }
-
-        assertEquals(3, clickCounter)
-    }
-
-    @Test
-    fun touch_hold_disabled() {
-        var clickCounter = 0
-
-        boxWithRepeatableClickable(rule, 500, false) {
-            clickCounter++
-        }
-
-        assertEquals(0, clickCounter)
-    }
-
-    private fun boxWithRepeatableClickable(
-        rule: ComposeContentTestRule,
-        holdDelay: Long,
-        enabled: Boolean = true,
-        initialDelay: Long = 500L,
-        incrementalDelay: Long = 60L,
-        onClick: () -> Unit
-    ) {
-
-        rule.setContent {
-            Box(
-                modifier = Modifier
-                    .testTag(TEST_TAG)
-                    .repeatableClickable(
-                        enabled = enabled,
-                        initialDelay = initialDelay,
-                        incrementalDelay = incrementalDelay
-                    ) {
-                        onClick()
-                    }
-            ) {}
-        }
-
-        rule.onNodeWithTag(TEST_TAG).performTouchInput {
-            down(center)
-            advanceEventTime(holdDelay)
-            up()
-        }
-    }
-}
diff --git a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/RepeatableClickableTest.kt b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/RepeatableClickableTest.kt
new file mode 100644
index 0000000..895034c
--- /dev/null
+++ b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/RepeatableClickableTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.materialcore
+
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.dp
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+
+public class RepeatableClickableTest {
+    @get:Rule
+    public val rule = createComposeRule()
+
+    @Test
+    fun touch_hold_shorter_than_threshold_performs_click() {
+        var repeatableClickCounter = 0
+        var clicked = false
+
+        boxWithRepeatableClickable(rule,
+            holdDelay = INITIAL_DELAY / 2,
+            onRepeatableClick = { repeatableClickCounter++ },
+            onClick = { clicked = true }
+        )
+        assertEquals(0, repeatableClickCounter)
+        assertEquals(true, clicked)
+    }
+
+    @Test
+    fun touch_hold_equals_to_threshold_performs_repeatable_click() {
+        var repeatableClickCounter = 0
+        var clicked = false
+
+        boxWithRepeatableClickable(rule,
+            holdDelay = INITIAL_DELAY,
+            onRepeatableClick = { repeatableClickCounter++ },
+            onClick = { clicked = true }
+        )
+        assertEquals(1, repeatableClickCounter)
+        assertEquals(false, clicked)
+    }
+
+    @Test
+    fun touch_hold_longer_than_threshold_performs_multiple_repeatable_clicks() {
+        var repeatableClickCounter = 0
+        var clicked = false
+
+        boxWithRepeatableClickable(rule,
+            holdDelay = INITIAL_DELAY + INCREMENTAL_DELAY * 2,
+            onRepeatableClick = { repeatableClickCounter++ },
+            onClick = { clicked = true }
+        )
+
+        assertEquals(3, repeatableClickCounter)
+        assertEquals(false, clicked)
+    }
+
+    @Test
+    fun touch_hold_disabled() {
+        var repeatableClickCounter = 0
+        var clicked = false
+
+        boxWithRepeatableClickable(rule,
+            holdDelay = INITIAL_DELAY,
+            enabled = false,
+            onRepeatableClick = { repeatableClickCounter++ },
+            onClick = { clicked = true }
+        )
+
+        assertEquals(0, repeatableClickCounter)
+        assertEquals(false, clicked)
+    }
+
+    @Test
+    fun touch_hold_release_outside_of_bounds_shorter_than_threshold() {
+        var repeatableClickCounter = 0
+        var clicked = false
+
+        boxWithRepeatableClickable(rule,
+            holdDelay = INITIAL_DELAY / 2,
+            enabled = true,
+            releaseOutsideOfBox = true,
+            onRepeatableClick = { repeatableClickCounter++ },
+            onClick = { clicked = true }
+        )
+
+        assertEquals(0, repeatableClickCounter)
+        assertEquals(false, clicked)
+    }
+
+    private fun boxWithRepeatableClickable(
+        rule: ComposeContentTestRule,
+        holdDelay: Long,
+        enabled: Boolean = true,
+        initialDelay: Long = INITIAL_DELAY,
+        incrementalDelay: Long = INCREMENTAL_DELAY,
+        releaseOutsideOfBox: Boolean = false,
+        onClick: () -> Unit,
+        onRepeatableClick: () -> Unit
+    ) {
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .fillMaxSize()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .testTag(TEST_TAG)
+                        .size(50.dp)
+                        .align(Alignment.Center)
+                        .repeatableClickable(
+                            enabled = enabled,
+                            initialDelay = initialDelay,
+                            incrementalDelay = incrementalDelay,
+                            indication = null,
+                            interactionSource = remember { MutableInteractionSource() },
+                            onClick = onClick,
+                            onRepeatableClick = onRepeatableClick
+                        )
+                ) {}
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            down(center)
+            advanceEventTime(holdDelay)
+            if (releaseOutsideOfBox) {
+                // Move to -1f,-1f coordinates which are outside of the current component
+                moveTo(Offset(-1f, -1f))
+            }
+            up()
+        }
+    }
+
+    companion object {
+        private const val INITIAL_DELAY = 500L
+        private const val INCREMENTAL_DELAY = 60L
+    }
+}
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/RepeatableClickable.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/RepeatableClickable.kt
index da01843..b5ac77f 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/RepeatableClickable.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/RepeatableClickable.kt
@@ -17,46 +17,106 @@
 package androidx.wear.compose.materialcore
 
 import androidx.annotation.RestrictTo
+import androidx.compose.foundation.Indication
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.semantics.Role
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
 /**
  * This modifier provides functionality to increment or decrement values repeatedly
- * by holding down the composable
+ * by holding down the composable.
+ * Should be used instead of clickable modifier to achieve clickable and repeatable
+ * clickable behavior. Can't be used along with clickable modifier as it already implements it.
+ *
+ * Callbacks [onClick] and [onRepeatableClick] are different. [onClick] is triggered only
+ * when the hold duration is shorter than [initialDelay] and no repeatable clicks happened.
+ * [onRepeatableClick] is repeatedly triggered when the hold duration is longer
+ * than [initialDelay] with [incrementalDelay] intervals.
+ *
+ * @param interactionSource [MutableInteractionSource] that will be used to dispatch
+ * [PressInteraction.Press] when this clickable is pressed. Only the initial (first) press will be
+ * recorded and dispatched with [MutableInteractionSource].
+ * @param indication indication to be shown when modified element is pressed. By default,
+ * indication from [LocalIndication] will be used. Pass `null` to show no indication, or
+ * current value from [LocalIndication] to show theme default
+ * @param enabled Controls the enabled state. When `false`, [onClick], and this modifier will
+ * appear disabled for accessibility services
+ * @param onClickLabel semantic / accessibility label for the [onClick] action
+ * @param role the type of user interface element. Accessibility services might use this
+ * to describe the element or do customizations
+ * @param initialDelay The initial delay before the click starts repeating, in ms
+ * @param incrementalDelay The delay between each repeated click, in ms
+ * @param onClick will be called when user clicks on the element
+ * @param onRepeatableClick will be called after the [initialDelay] with [incrementalDelay]
+ * between each call until the touch is released
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public fun Modifier.repeatableClickable(
-    enabled: Boolean,
+fun Modifier.repeatableClickable(
+    interactionSource: MutableInteractionSource,
+    indication: Indication?,
+    enabled: Boolean = true,
+    onClickLabel: String? = null,
+    role: Role? = null,
     initialDelay: Long = 500L,
     incrementalDelay: Long = 60L,
-    onClick: () -> Unit
+    onClick: () -> Unit,
+    onRepeatableClick: () -> Unit = onClick
 ): Modifier = composed {
-
+    val currentOnRepeatableClick by rememberUpdatedState(onRepeatableClick)
     val currentOnClick by rememberUpdatedState(onClick)
+    // This flag is used for checking whether the onClick should be ignored or not.
+    // If this flag is true, then it means that repeatable click happened and onClick
+    // shouldn't be triggered.
+    var ignoreOnClick by remember { mutableStateOf(false) }
 
-    pointerInput(enabled) {
-        coroutineScope {
-            awaitEachGesture {
-                awaitFirstDown()
-                val repeatingJob = launch {
-                    delay(initialDelay)
-                    while (enabled) {
-                        currentOnClick()
-                        delay(incrementalDelay)
+    // Repeatable logic should always follow the clickable, as the lowest modifier finishes first,
+    // and we have to be sure that repeatable goes before clickable.
+    clickable(
+        interactionSource = interactionSource,
+        indication = indication,
+        enabled = enabled,
+        onClickLabel = onClickLabel,
+        role = role,
+        onClick = {
+            if (!ignoreOnClick) {
+                currentOnClick()
+            }
+            ignoreOnClick = false
+        },
+    )
+        .pointerInput(enabled) {
+            coroutineScope {
+                awaitEachGesture {
+                    awaitFirstDown()
+                    ignoreOnClick = false
+                    val repeatingJob = launch {
+                        delay(initialDelay)
+                        ignoreOnClick = true
+                        while (enabled) {
+                            currentOnRepeatableClick()
+                            delay(incrementalDelay)
+                        }
                     }
+                    // Waiting for up or cancellation of the gesture.
+                    waitForUpOrCancellation()
+                    repeatingJob.cancel()
                 }
-                waitForUpOrCancellation()
-                repeatingJob.cancel()
             }
         }
-    }
 }
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Slider.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Slider.kt
index 065d3d0..637a82c 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Slider.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Slider.kt
@@ -18,7 +18,6 @@
 
 import androidx.annotation.RestrictTo
 import androidx.compose.foundation.LocalIndication
-import androidx.compose.foundation.clickable
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxHeight
@@ -48,13 +47,12 @@
         modifier = Modifier
             .width(buttonControlSize)
             .fillMaxHeight()
-            .clickable(
+            .repeatableClickable(
                 enabled = enabled,
                 onClick = onClick,
                 interactionSource = remember { MutableInteractionSource() },
-                indication = LocalIndication.current,
+                indication = LocalIndication.current
             )
-            .repeatableClickable(enabled = enabled, onClick = onClick)
             .then(modifier),
         contentAlignment = contentAlignment
     ) {
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Stepper.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Stepper.kt
index d1a25d0..70428a4 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Stepper.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Stepper.kt
@@ -18,7 +18,6 @@
 
 import androidx.annotation.RestrictTo
 import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
 import androidx.compose.foundation.indication
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Arrangement
@@ -39,7 +38,6 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.unit.dp
 
 /**
@@ -156,10 +154,12 @@
         modifier = Modifier
             .fillMaxWidth()
             .weight(StepperDefaults.ButtonWeight)
-            .clickable(
-                interactionSource, null, onClick = onClick, enabled = enabled, role = Role.Button
+            .repeatableClickable(
+                enabled = enabled,
+                onClick = onClick,
+                interactionSource = interactionSource,
+                indication = null
             )
-            .repeatableClickable(enabled = enabled, onClick = onClick)
             .wrapContentWidth()
             .indication(interactionSource, rememberRipple(bounded = false))
             .padding(paddingValues),