Merge "Fix flaky room-paging test loadEverything_inReverse" into androidx-main
diff --git a/activity/activity-compose/build.gradle b/activity/activity-compose/build.gradle
index b89c2b2..8ef2c0b 100644
--- a/activity/activity-compose/build.gradle
+++ b/activity/activity-compose/build.gradle
@@ -37,7 +37,7 @@
     // Outside of androidx this is resolved via constraint added to lifecycle-common,
     // but it doesn't work in androidx.
     // See aosp/1804059
-    implementation(project(":lifecycle:lifecycle-common-java8"))
+    implementation("androidx.lifecycle:lifecycle-common-java8:2.5.0-rc01")
 
     androidTestImplementation projectOrArtifact(":compose:ui:ui-test-junit4")
     androidTestImplementation projectOrArtifact(":compose:material:material")
diff --git a/activity/activity-ktx/build.gradle b/activity/activity-ktx/build.gradle
index 8c0d82b..21e4e1a 100644
--- a/activity/activity-ktx/build.gradle
+++ b/activity/activity-ktx/build.gradle
@@ -28,11 +28,11 @@
     api("androidx.core:core-ktx:1.1.0") {
         because "Mirror activity dependency graph for -ktx artifacts"
     }
-    api(projectOrArtifact(":lifecycle:lifecycle-runtime-ktx")) {
+    api("androidx.lifecycle:lifecycle-runtime-ktx:2.5.0-rc01") {
         because 'Mirror activity dependency graph for -ktx artifacts'
     }
-    api(projectOrArtifact(":lifecycle:lifecycle-viewmodel-ktx"))
-    api(projectOrArtifact(":savedstate:savedstate-ktx")) {
+    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-rc01")
+    api("androidx.savedstate:savedstate-ktx:1.2.0-rc01") {
         because 'Mirror activity dependency graph for -ktx artifacts'
     }
     api(libs.kotlinStdlib)
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index f30a135..91a8e4d 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -17,11 +17,11 @@
 dependencies {
     api("androidx.annotation:annotation:1.1.0")
     implementation("androidx.collection:collection:1.0.0")
-    api(projectOrArtifact(":core:core"))
-    api(projectOrArtifact(":lifecycle:lifecycle-runtime"))
-    api(projectOrArtifact(":lifecycle:lifecycle-viewmodel"))
-    api(projectOrArtifact(":savedstate:savedstate"))
-    api(projectOrArtifact(":lifecycle:lifecycle-viewmodel-savedstate"))
+    api("androidx.core:core:1.8.0-rc01")
+    api("androidx.lifecycle:lifecycle-runtime:2.5.0-rc01")
+    api("androidx.lifecycle:lifecycle-viewmodel:2.5.0-rc01")
+    api("androidx.savedstate:savedstate:1.2.0-rc01")
+    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.0-rc01")
     implementation("androidx.tracing:tracing:1.0.0")
     api(libs.kotlinStdlib)
 
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index ca45019..06820cb 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -11,8 +11,8 @@
     api("androidx.annotation:annotation:1.3.0")
     api(project(":core:core"))
 
-    implementation(project(":emoji2:emoji2"))
-    implementation(project(":emoji2:emoji2-views-helper"))
+    implementation("androidx.emoji2:emoji2:1.2.0-alpha04")
+    implementation("androidx.emoji2:emoji2-views-helper:1.2.0-alpha04")
     implementation("androidx.collection:collection:1.0.0")
     api("androidx.cursoradapter:cursoradapter:1.0.0")
     api(project(":activity:activity"))
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradleConfigurationTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradleConfigurationTask.kt
index 21d5323..1fff94c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradleConfigurationTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradleConfigurationTask.kt
@@ -17,6 +17,8 @@
 package androidx.build.playground
 
 import com.google.common.annotations.VisibleForTesting
+import java.io.File
+import java.util.Properties
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
 import org.gradle.api.Project
@@ -25,8 +27,6 @@
 import org.gradle.api.tasks.OutputFile
 import org.gradle.api.tasks.TaskAction
 import org.gradle.api.tasks.TaskProvider
-import java.io.File
-import java.util.Properties
 
 /**
  * Compares the playground Gradle configuration with the main androidx Gradle configuration
@@ -71,10 +71,12 @@
             playgroundGradleWrapper.get().asFile
         )
         if (androidxGradleVersion != playgroundGradleVersion) {
-            throw GradleException("""
+            throw GradleException(
+                """
                 Playground gradle version ($playgroundGradleVersion) must match the AndroidX main
                 build gradle version ($androidxGradleVersion).
-            """.trimIndent())
+                """.trimIndent()
+            )
         }
     }
 
@@ -99,15 +101,26 @@
         // this includes properties that are not defined in the root androidx build as they might
         // be properties which can alter the build output. We might consider allow listing certain
         // properties in the future if necessary.
-        playgroundProperties.forEach {
-            val rootValue = rootProperties[it.key]
-            if (rootValue != it.value && it.key != "org.gradle.jvmargs") {
+        val propertyKeys = rootProperties.keys + playgroundProperties.keys
+        propertyKeys.forEach { key ->
+            val rootValue = rootProperties[key]
+            val playgroundValue = playgroundProperties[key]
+
+            if (rootValue != playgroundValue &&
+                !ignoredProperties.contains(key) &&
+                exceptedProperties[key] != playgroundValue
+            ) {
                 throw GradleException(
                     """
-                    ${it.key} is defined as ${it.value} in playground properties but
-                    it does not match the value defined in root properties file ($rootValue).
-                    Having inconsistent properties in playground projects might trigger wrong
-                    compilation output in the main AndroidX build, thus not allowed.
+                    $key is defined in ${androidxProperties.get().asFile.absolutePath} as
+                    $rootValue, which differs from $playgroundValue defined in
+                    ${this.playgroundProperties.get().asFile.absolutePath}. If this change is
+                    intentional, you can ignore it by adding it to ignoredProperties in
+                    VerifyPlaygroundGradleConfigurationTask.kt
+
+                    Note: Having inconsistent properties in playground projects might trigger wrong
+                    compilation output in the main AndroidX build, so if a property is defined in
+                    playground properties, its value **MUST** match that of regular AndroidX build.
                     """.trimIndent()
                 )
             }
@@ -123,6 +136,21 @@
     companion object {
         private const val TASK_NAME = "verifyPlaygroundGradleConfiguration"
 
+        // A mapping of the expected override in playground, which should generally follow AOSP on
+        // androidx-main. Generally, should only be used for conflicting properties which have
+        // different values in different built targets on AOSP, but still should be declared in
+        // playground.
+        private val exceptedProperties = mapOf(
+            "androidx.writeVersionedApiFiles" to "true",
+        )
+
+        private val ignoredProperties = setOf(
+            "org.gradle.jvmargs",
+            "org.gradle.daemon",
+            "android.builder.sdkDownload",
+            "android.suppressUnsupportedCompileSdk",
+        )
+
         /**
          * Regular expression to extract the gradle version from a distributionUrl property.
          * Sample input looks like: <some-path>/gradle-7.3-rc-2-all.zip
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBuffer.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBuffer.java
new file mode 100644
index 0000000..aca7060
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBuffer.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayDeque;
+
+/**
+ * Implements {@link RingBuffer} with an {@link ArrayDeque}.
+ *
+ * @param <T> the type of elements stored in the RingBuffer.
+ */
+public class ArrayRingBuffer<T> implements RingBuffer<T> {
+
+    private final int mRingBufferCapacity;
+    private final ArrayDeque<T> mBuffer;
+    @Nullable  final OnRemoveCallback<T> mOnRemoveCallback;
+
+    public ArrayRingBuffer(int ringBufferCapacity) {
+        this(ringBufferCapacity, null);
+    }
+
+    public ArrayRingBuffer(int ringBufferCapacity, @Nullable OnRemoveCallback<T> onRemoveCallback) {
+        mRingBufferCapacity = ringBufferCapacity;
+        mBuffer = new ArrayDeque<>(mRingBufferCapacity);
+        mOnRemoveCallback = onRemoveCallback;
+    }
+
+    @Override
+    public void enqueue(@NonNull T element) {
+        if (mBuffer.size() >= mRingBufferCapacity) {
+            T removedItem = this.dequeue();
+            if (mOnRemoveCallback != null) {
+                mOnRemoveCallback.onRemove(removedItem);
+            }
+        }
+        mBuffer.addFirst(element);
+    }
+
+    @Override
+    public @NonNull T dequeue() {
+        T removedElement = mBuffer.removeLast();
+        return removedElement;
+    }
+
+    @Override
+    public int getMaxCapacity() {
+        return mRingBufferCapacity;
+    }
+}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBufferTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBufferTest.kt
new file mode 100644
index 0000000..d147aa7
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBufferTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.camera.camera2.internal.util
+
+import androidx.camera.camera2.internal.util.RingBuffer.OnRemoveCallback
+import androidx.testutils.assertThrows
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(JUnit4::class)
+class ArrayRingBufferTest {
+
+    @Test
+    fun testEnqueue() {
+        val testBuffer: RingBuffer<Int> =
+            androidx.camera.camera2.internal.util.ArrayRingBuffer(3)
+        testBuffer.enqueue(1)
+        testBuffer.enqueue(2)
+        testBuffer.enqueue(3)
+        testBuffer.enqueue(4)
+        assertThat(testBuffer.dequeue()).isEqualTo(2)
+    }
+
+    @Test
+    fun testDequeue_correctValueIsDequeued() {
+        @Suppress("UNCHECKED_CAST")
+        val mockCallback: OnRemoveCallback<Int> = mock(
+            OnRemoveCallback::class.java) as OnRemoveCallback<Int>
+
+        val testBuffer: RingBuffer<Int> =
+            androidx.camera.camera2.internal.util.ArrayRingBuffer(
+                3,
+                mockCallback
+            )
+        testBuffer.enqueue(1)
+        testBuffer.enqueue(2)
+        testBuffer.enqueue(3)
+        assertThat(testBuffer.dequeue()).isEqualTo(1)
+        verify(mockCallback, times(0)).onRemove(any())
+    }
+
+    @Test
+    fun testDequeue_OnRemoveCallbackCalledOnlyWhenDiscardingItemsDueToCapacity() {
+        @Suppress("UNCHECKED_CAST")
+        val mockCallback: OnRemoveCallback<Int> = mock(
+            OnRemoveCallback::class.java) as OnRemoveCallback<Int>
+
+        val testBuffer: RingBuffer<Int> =
+            androidx.camera.camera2.internal.util.ArrayRingBuffer(
+                3,
+                mockCallback
+            )
+        testBuffer.enqueue(1)
+        testBuffer.enqueue(2)
+        testBuffer.enqueue(3)
+        testBuffer.enqueue(4)
+        verify(mockCallback).onRemove(1)
+        assertThat(testBuffer.dequeue()).isEqualTo(2)
+        verify(mockCallback, times(1)).onRemove(any())
+    }
+
+    @Test()
+    fun testDequeue_exceptionThrownWhenBufferEmpty() {
+        val testBuffer: RingBuffer<Int> =
+            androidx.camera.camera2.internal.util.ArrayRingBuffer(5)
+        assertThrows(NoSuchElementException::class.java, testBuffer::dequeue)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBuffer.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBuffer.java
new file mode 100644
index 0000000..0b70d28
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBuffer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageInfo;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.impl.CameraCaptureMetaData.AeState;
+import androidx.camera.core.impl.CameraCaptureMetaData.AfState;
+import androidx.camera.core.impl.CameraCaptureMetaData.AwbState;
+import androidx.camera.core.impl.CameraCaptureResult;
+import androidx.camera.core.impl.CameraCaptureResults;
+
+/**
+ * Used for storing frames for ZSL capture.
+ *
+ * <p>Enqueueing process ignores frames when the quality is inadequate for ZSL capture.</p>
+ *
+ * <p>Adequate quality is defined as:
+ * - AF Focused
+ * - AE Converged
+ * - AWB Converged
+ * </p>
+ */
+@RequiresApi(21)
+public final class ZslRingBuffer extends ArrayRingBuffer<ImageProxy> {
+
+    public ZslRingBuffer(int ringBufferCapacity,
+            @NonNull OnRemoveCallback<ImageProxy> onRemoveCallback) {
+        super(ringBufferCapacity, onRemoveCallback);
+    }
+
+    @Override
+    public void enqueue(@NonNull ImageProxy imageProxy) {
+        if (isValidZslFrame(imageProxy.getImageInfo())) {
+            super.enqueue(imageProxy);
+        } else {
+            mOnRemoveCallback.onRemove(imageProxy);
+        }
+    }
+
+    private boolean isValidZslFrame(@NonNull ImageInfo imageInfo) {
+        CameraCaptureResult cameraCaptureResult =
+                CameraCaptureResults.retrieveCameraCaptureResult(imageInfo);
+
+        if (cameraCaptureResult.getAfState() != AfState.LOCKED_FOCUSED
+                && cameraCaptureResult.getAfState() != AfState.PASSIVE_FOCUSED)  {
+            return false;
+        }
+
+        if (cameraCaptureResult.getAeState() != AeState.CONVERGED) {
+            return false;
+        }
+
+        if (cameraCaptureResult.getAwbState() != AwbState.CONVERGED) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBufferTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBufferTest.kt
new file mode 100644
index 0000000..4fcaff0
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBufferTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.camera.camera2.internal.util
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.core.ImageInfo
+import androidx.camera.core.ImageProxy
+import androidx.camera.core.impl.CameraCaptureMetaData.AeState
+import androidx.camera.core.impl.CameraCaptureMetaData.AfState
+import androidx.camera.core.impl.CameraCaptureMetaData.AwbState
+import androidx.camera.core.impl.CameraCaptureResult
+import androidx.camera.core.internal.CameraCaptureResultImageInfo
+import androidx.camera.camera2.internal.util.RingBuffer.OnRemoveCallback
+import androidx.camera.testing.fakes.FakeImageProxy
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(JUnit4::class)
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+class ZslRingBufferTest {
+
+    lateinit var mMockedCameraCaptureResult: CameraCaptureResult
+
+    @Before
+    fun setup() {
+        mMockedCameraCaptureResult = mock(CameraCaptureResult::class.java)
+        `when`(mMockedCameraCaptureResult.aeState).thenReturn(AeState.CONVERGED)
+        `when`(mMockedCameraCaptureResult.afState).thenReturn(AfState.LOCKED_FOCUSED)
+        `when`(mMockedCameraCaptureResult.awbState).thenReturn(AwbState.CONVERGED)
+    }
+
+    @Test
+    fun enqueue_ensureOldFramesAreRemoved() {
+        @Suppress("UNCHECKED_CAST")
+        val onRemoveCallback = mock(OnRemoveCallback::class.java) as OnRemoveCallback<ImageProxy>
+        val ringBuffer = androidx.camera.camera2.internal.util.ZslRingBuffer(
+            2,
+            onRemoveCallback
+        )
+
+        val imageInfo: ImageInfo = CameraCaptureResultImageInfo(mMockedCameraCaptureResult)
+
+        val imageProxy1 = FakeImageProxy(imageInfo)
+        ringBuffer.enqueue(imageProxy1)
+        ringBuffer.enqueue(FakeImageProxy(imageInfo))
+        ringBuffer.enqueue(FakeImageProxy(imageInfo))
+
+        verify(onRemoveCallback).onRemove(imageProxy1)
+        verify(onRemoveCallback, times(1)).onRemove(any())
+    }
+
+    @Test
+    fun enqueue_framesWithBad3AStatesNotQueued() {
+        @Suppress("UNCHECKED_CAST")
+        val onRemoveCallback = mock(OnRemoveCallback::class.java) as OnRemoveCallback<ImageProxy>
+        val ringBuffer = androidx.camera.camera2.internal.util.ZslRingBuffer(
+            2,
+            onRemoveCallback
+        )
+
+        val imageInfo: ImageInfo = CameraCaptureResultImageInfo(mMockedCameraCaptureResult)
+
+        val imageProxy1 = FakeImageProxy(imageInfo)
+        ringBuffer.enqueue(imageProxy1)
+
+        `when`(mMockedCameraCaptureResult.aeState).thenReturn(AeState.SEARCHING)
+        val imageProxy2 = FakeImageProxy(imageInfo)
+        ringBuffer.enqueue(imageProxy2)
+        verify(onRemoveCallback, times(1)).onRemove(imageProxy2)
+        `when`(mMockedCameraCaptureResult.aeState).thenReturn(AeState.CONVERGED)
+
+        `when`(mMockedCameraCaptureResult.afState).thenReturn(AfState.PASSIVE_NOT_FOCUSED)
+        val imageProxy3 = FakeImageProxy(imageInfo)
+        ringBuffer.enqueue(imageProxy3)
+        verify(onRemoveCallback, times(1)).onRemove(imageProxy3)
+        `when`(mMockedCameraCaptureResult.afState).thenReturn(AfState.PASSIVE_NOT_FOCUSED)
+
+        `when`(mMockedCameraCaptureResult.awbState).thenReturn(AwbState.METERING)
+        val imageProxy4 = FakeImageProxy(imageInfo)
+        ringBuffer.enqueue(imageProxy4)
+        verify(onRemoveCallback, times(1)).onRemove(imageProxy4)
+        `when`(mMockedCameraCaptureResult.awbState).thenReturn(AwbState.CONVERGED)
+
+        verify(onRemoveCallback, times(3)).onRemove(any())
+        assertThat(ringBuffer.dequeue()).isEqualTo(imageProxy1)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
index bbbe4d1..d0b4e82 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
@@ -38,7 +38,7 @@
 import androidx.camera.camera2.internal.compat.CameraManagerCompat;
 import androidx.camera.camera2.internal.compat.workaround.ExcludedSupportedSizesContainer;
 import androidx.camera.camera2.internal.compat.workaround.ExtraSupportedSurfaceCombinationsContainer;
-import androidx.camera.camera2.internal.compat.workaround.ResolutionSelector;
+import androidx.camera.camera2.internal.compat.workaround.ResolutionCorrector;
 import androidx.camera.camera2.internal.compat.workaround.TargetAspectRatio;
 import androidx.camera.core.AspectRatio;
 import androidx.camera.core.CameraUnavailableException;
@@ -101,7 +101,7 @@
     private Map<Integer, Size[]> mOutputSizesCache = new HashMap<>();
     @NonNull
     private final DisplayInfoManager mDisplayInfoManager;
-    private final ResolutionSelector mResolutionSelector = new ResolutionSelector();
+    private final ResolutionCorrector mResolutionCorrector = new ResolutionCorrector();
 
     SupportedSurfaceCombination(@NonNull Context context, @NonNull String cameraId,
             @NonNull CameraManagerCompat cameraManagerCompat,
@@ -478,7 +478,7 @@
             }
         }
 
-        supportedResolutions = mResolutionSelector.insertOrPrioritize(
+        supportedResolutions = mResolutionCorrector.insertOrPrioritize(
                 getConfigType(config.getInputFormat()),
                 supportedResolutions);
 
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelector.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrector.java
similarity index 91%
rename from camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelector.java
rename to camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrector.java
index 8812500..467ceb8 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelector.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrector.java
@@ -34,23 +34,23 @@
  * quirks.
  */
 @RequiresApi(21)
-public class ResolutionSelector {
+public class ResolutionCorrector {
 
     @Nullable
     private final ExtraCroppingQuirk mExtraCroppingQuirk;
 
     /**
-     * Constructs new {@link ResolutionSelector}.
+     * Constructs new {@link ResolutionCorrector}.
      */
-    public ResolutionSelector() {
+    public ResolutionCorrector() {
         this(DeviceQuirks.get(ExtraCroppingQuirk.class));
     }
 
     /**
-     * Constructs new {@link ResolutionSelector}.
+     * Constructs new {@link ResolutionCorrector}.
      */
     @VisibleForTesting
-    ResolutionSelector(@Nullable ExtraCroppingQuirk extraCroppingQuirk) {
+    ResolutionCorrector(@Nullable ExtraCroppingQuirk extraCroppingQuirk) {
         mExtraCroppingQuirk = extraCroppingQuirk;
     }
 
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionSelectorQuirkTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionCorrectorQuirkTest.java
similarity index 97%
rename from camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionSelectorQuirkTest.java
rename to camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionCorrectorQuirkTest.java
index e1ce2e4..6faf911 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionSelectorQuirkTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionCorrectorQuirkTest.java
@@ -33,7 +33,7 @@
 @RunWith(RobolectricTestRunner.class)
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class ResolutionSelectorQuirkTest {
+public class ResolutionCorrectorQuirkTest {
 
 
     @Test
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelectorTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrectorTest.kt
similarity index 89%
rename from camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelectorTest.kt
rename to camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrectorTest.kt
index 6f2d99f..98426d4 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelectorTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrectorTest.kt
@@ -38,14 +38,14 @@
 private val SUPPORTED_RESOLUTIONS = listOf(RESOLUTION_1, RESOLUTION_2)
 
 /**
- * Unit test for [ResolutionSelector].
+ * Unit test for [ResolutionCorrector].
  */
 @RunWith(RobolectricTestRunner::class)
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-class ResolutionSelectorTest {
+class ResolutionCorrectorTest {
 
-    private val mResolutionSelector = ResolutionSelector(object : ExtraCroppingQuirk() {
+    private val mResolutionCorrector = ResolutionCorrector(object : ExtraCroppingQuirk() {
         override fun getVerifiedResolution(configType: SurfaceConfig.ConfigType): Size? {
             return when (configType) {
                 SurfaceConfig.ConfigType.YUV -> SELECT_RESOLUTION_YUV
@@ -77,7 +77,7 @@
     ) {
         val resolutions: MutableList<Size> = ArrayList<Size>(SUPPORTED_RESOLUTIONS)
         resolutions.add(resolution)
-        Truth.assertThat(mResolutionSelector.insertOrPrioritize(configType, resolutions))
+        Truth.assertThat(mResolutionCorrector.insertOrPrioritize(configType, resolutions))
             .containsExactly(resolution, RESOLUTION_1, RESOLUTION_2).inOrder()
     }
 
@@ -100,7 +100,7 @@
         configType: SurfaceConfig.ConfigType,
         resolution: Size
     ) {
-        Truth.assertThat(mResolutionSelector.insertOrPrioritize(configType, SUPPORTED_RESOLUTIONS))
+        Truth.assertThat(mResolutionCorrector.insertOrPrioritize(configType, SUPPORTED_RESOLUTIONS))
             .containsExactly(resolution, RESOLUTION_1, RESOLUTION_2).inOrder()
     }
 
@@ -117,8 +117,8 @@
     private fun noQuirk_returnsOriginalSupportedResolutions(
         quirk: ExtraCroppingQuirk?
     ) {
-        val resolutionSelector = ResolutionSelector(quirk)
-        val result = resolutionSelector.insertOrPrioritize(
+        val resolutionCorrector = ResolutionCorrector(quirk)
+        val result = resolutionCorrector.insertOrPrioritize(
             SurfaceConfig.ConfigType.PRIV,
             SUPPORTED_RESOLUTIONS
         )
diff --git a/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/UnrememberedAnimatableDetector.kt b/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/UnrememberedAnimatableDetector.kt
index 18d29c2..adcf925 100644
--- a/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/UnrememberedAnimatableDetector.kt
+++ b/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/UnrememberedAnimatableDetector.kt
@@ -21,7 +21,7 @@
 import androidx.compose.lint.Name
 import androidx.compose.lint.Names
 import androidx.compose.lint.isInPackageName
-import androidx.compose.lint.invokedInComposableBodyAndNotRemembered
+import androidx.compose.lint.isNotRemembered
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
@@ -61,7 +61,7 @@
                 if (!returnType.rawType().equalsToText(Animatable.javaFqn)) return
             }
 
-            if (node.invokedInComposableBodyAndNotRemembered()) {
+            if (node.isNotRemembered()) {
                 context.report(
                     UnrememberedAnimatable,
                     node,
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
index 89e8c65..10d886e 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
@@ -139,7 +139,9 @@
             navigation(startDestination = innerStartRoute, route = "Parent") {
                 // ...
                 composable("exampleWithRoute") { backStackEntry ->
-                    val parentEntry = remember { navController.getBackStackEntry("Parent") }
+                    val parentEntry = remember(backStackEntry) {
+                        navController.getBackStackEntry("Parent")
+                    }
                     val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
                     ExampleWithRouteScreen(parentViewModel)
                 }
diff --git a/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt b/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
index 7429971..f601de0 100644
--- a/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
+++ b/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
@@ -348,6 +348,37 @@
         """
     )
 
+    val PaddingValues: TestFile = compiledStub(
+        filename = "Padding.kt",
+        filepath = "androidx/compose/foundation/layout",
+        checksum = 0xeedd3f96,
+        """
+
+            package androidx.compose.foundation.layout
+
+            import androidx.compose.ui.Modifier
+
+            interface PaddingValues
+
+        """,
+        """
+        META-INF/main.kotlin_module:
+        H4sIAAAAAAAAAGNgYGBmYGBgBGI2BijgUueSTMxLKcrPTKnQS87PLcgvTtXL
+        TSxJLcpMzBHiCk5OTEvLz0nxLuHi5WJOy88XYgtJLS7xLlFi0GIAACJwI+tQ
+        AAAA
+        """,
+        """
+        androidx/compose/foundation/layout/PaddingValues.class:
+        H4sIAAAAAAAAAJVOTUvDQBB9s9Gkxq9ULdQ/YdrizZMXIVBRFHrJaZtsyzbp
+        rnQ3pd76uzxIz/4ocVL9A87Amzfz4L35+v74BHCLHmEgTbmyutykhV2+WafS
+        mW1MKb22Jq3lu218+izLUpv5RNaNchGIkCzkWrJs5unTdKEKHyEgdMeV9bU2
+        6aPyki3kHUEs1wFnUQthCyBQxfeNbrcBs3JI6O22nVj0RSwSZrP+bjsSA2rF
+        EWE0/u+THMw58d/tpvK8vNpmVagHXSvC9UtjvF6qiXZ6Wqt7Y6zfu7mQM3GA
+        3xK43OMFrngO2fKQO8wRZIgydDIcIWaK4wwnOM1BDmc4zyEcEofuD692uKBp
+        AQAA
+        """
+    )
+
     val Remember: TestFile = compiledStub(
         filename = "Remember.kt",
         filepath = "androidx/compose/runtime",
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
index e725baf..339c299 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
@@ -45,21 +45,44 @@
 import org.jetbrains.uast.withContainingElements
 
 /**
- * Returns whether this [UCallExpression] is invoked within the body of a Composable function or
- * lambda, and is not `remember`ed.
- *
- * This searches parent declarations until we find a lambda expression or a function, and looks
- * to see if these are Composable. If they are Composable, this function returns whether or not
- * this call expression is inside the block of a `remember` call.
+ * Returns whether this [UCallExpression] is directly invoked within the body of a Composable
+ * function or lambda without being `remember`ed.
  */
-fun UCallExpression.invokedInComposableBodyAndNotRemembered(): Boolean {
+fun UCallExpression.isNotRemembered(): Boolean = isNotRememberedWithKeys()
+
+/**
+ * Returns whether this [UCallExpression] is directly invoked within the body of a Composable
+ * function or lambda without being `remember`ed, or whether it is invoked inside a `remember call
+ * without the provided [keys][keyClassNames].
+ * - Returns true if this [UCallExpression] is directly invoked inside a Composable function or
+ * lambda without being `remember`ed
+ * - Returns true if this [UCallExpression] is invoked inside a call to `remember`, but without all
+ * of the provided [keys][keyClassNames] being used as key parameters to `remember`
+ * - Returns false if this [UCallExpression] is correctly `remember`ed with the provided
+ * [keys][keyClassNames], or is not called inside a `remember` block, and is not called inside a
+ * Composable function or lambda
+ *
+ * @param keyClassNames [Name]s representing the expected classes that should be used as a key
+ * parameter to the `remember` call
+ */
+fun UCallExpression.isNotRememberedWithKeys(vararg keyClassNames: Name): Boolean {
     val visitor = ComposableBodyVisitor(this)
-    if (!visitor.isComposable()) return false
-    return visitor.parentUElements().none {
-        (it as? UCallExpression)?.let { element ->
-            element.methodName == Names.Runtime.Remember.shortName &&
-                element.resolve()?.isInPackageName(Names.Runtime.PackageName) == true
-        } == true
+    // The nearest method or lambda expression that contains this call expression
+    val boundaryElement = visitor.parentUElements().last()
+    // Check if the nearest lambda expression is actually a call to remember
+    val rememberCall: UCallExpression? = (boundaryElement.uastParent as? UCallExpression)?.takeIf {
+        it.methodName == Names.Runtime.Remember.shortName &&
+            it.resolve()?.isInPackageName(Names.Runtime.PackageName) == true
+    }
+    return if (rememberCall == null) {
+        visitor.isComposable()
+    } else {
+        val parameterTypes = rememberCall.valueArguments.mapNotNull {
+            it.getExpressionType()?.canonicalText
+        }
+        !keyClassNames.all {
+            parameterTypes.contains(it.javaFqn)
+        }
     }
 }
 
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
index 7423dbe..016e112 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
@@ -28,9 +28,6 @@
     object AnimationCore {
         val PackageName = Package("androidx.compose.animation.core")
     }
-    object Material {
-        val PackageName = Package("androidx.compose.material")
-    }
     object Runtime {
         val PackageName = Package("androidx.compose.runtime")
 
diff --git a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
index 11cbd84..0e1a911 100644
--- a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
+++ b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
@@ -19,8 +19,8 @@
 package androidx.compose.material.lint
 
 import androidx.compose.lint.Name
-import androidx.compose.lint.Names
 import androidx.compose.lint.isInPackageName
+import androidx.compose.material.lint.MaterialNames.Material
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
@@ -55,7 +55,7 @@
     override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
         override fun visitCallExpression(node: UCallExpression) {
             val method = node.resolve() ?: return
-            if (!method.isInPackageName(Names.Material.PackageName)) return
+            if (!method.isInPackageName(Material.PackageName)) return
 
             if (node.isConstructorCall()) {
                 if (method.containingClass?.name != Colors.shortName) return
@@ -229,6 +229,6 @@
     "error" to "onError"
 )
 
-private val LightColors = Name(Names.Material.PackageName, "lightColors")
-private val DarkColors = Name(Names.Material.PackageName, "darkColors")
-private val Colors = Name(Names.Material.PackageName, "Colors")
+private val LightColors = Name(Material.PackageName, "lightColors")
+private val DarkColors = Name(Material.PackageName, "darkColors")
+private val Colors = Name(Material.PackageName, "Colors")
diff --git a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialNames.kt b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialNames.kt
new file mode 100644
index 0000000..dae87fd
--- /dev/null
+++ b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialNames.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material.lint
+
+import androidx.compose.lint.Package
+
+/**
+ * Contains common names used for Material lint checks.
+ */
+object MaterialNames {
+    object Material {
+        val PackageName = Package("androidx.compose.material")
+    }
+}
diff --git a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ScaffoldPaddingDetector.kt b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ScaffoldPaddingDetector.kt
index 252ab25..870a673 100644
--- a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ScaffoldPaddingDetector.kt
+++ b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ScaffoldPaddingDetector.kt
@@ -17,9 +17,9 @@
 package androidx.compose.material.lint
 
 import androidx.compose.lint.Name
-import androidx.compose.lint.Names
 import androidx.compose.lint.findUnreferencedParameters
 import androidx.compose.lint.isInPackageName
+import androidx.compose.material.lint.MaterialNames.Material
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Implementation
@@ -44,7 +44,7 @@
     override fun getApplicableMethodNames(): List<String> = listOf(Scaffold.shortName)
 
     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
-        if (method.isInPackageName(Names.Material.PackageName)) {
+        if (method.isInPackageName(Material.PackageName)) {
             val contentArgument = computeKotlinArgumentMapping(node, method)
                 .orEmpty()
                 .filter { (_, parameter) ->
@@ -86,4 +86,4 @@
     }
 }
 
-private val Scaffold = Name(Names.Material.PackageName, "Scaffold")
\ No newline at end of file
+private val Scaffold = Name(Material.PackageName, "Scaffold")
\ No newline at end of file
diff --git a/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ScaffoldPaddingDetectorTest.kt b/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ScaffoldPaddingDetectorTest.kt
index 88b9576..7537ea8 100644
--- a/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ScaffoldPaddingDetectorTest.kt
+++ b/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ScaffoldPaddingDetectorTest.kt
@@ -39,38 +39,6 @@
     override fun getIssues(): MutableList<Issue> =
         mutableListOf(ScaffoldPaddingDetector.UnusedMaterialScaffoldPaddingParameter)
 
-    private val PaddingValuesStub = compiledStub(
-        filename = "Padding.kt",
-        filepath = "androidx/compose/foundation/layout",
-        checksum = 0xeedd3f96,
-        """
-
-            package androidx.compose.foundation.layout
-
-            import androidx.compose.ui.Modifier
-
-            interface PaddingValues
-
-        """,
-        """
-        META-INF/main.kotlin_module:
-        H4sIAAAAAAAAAGNgYGBmYGBgBGI2BijgUueSTMxLKcrPTKnQS87PLcgvTtXL
-        TSxJLcpMzBHiCk5OTEvLz0nxLuHi5WJOy88XYgtJLS7xLlFi0GIAACJwI+tQ
-        AAAA
-        """,
-        """
-        androidx/compose/foundation/layout/PaddingValues.class:
-        H4sIAAAAAAAAAJVOTUvDQBB9s9Gkxq9ULdQ/YdrizZMXIVBRFHrJaZtsyzbp
-        rnQ3pd76uzxIz/4ocVL9A87Amzfz4L35+v74BHCLHmEgTbmyutykhV2+WafS
-        mW1MKb22Jq3lu218+izLUpv5RNaNchGIkCzkWrJs5unTdKEKHyEgdMeV9bU2
-        6aPyki3kHUEs1wFnUQthCyBQxfeNbrcBs3JI6O22nVj0RSwSZrP+bjsSA2rF
-        EWE0/u+THMw58d/tpvK8vNpmVagHXSvC9UtjvF6qiXZ6Wqt7Y6zfu7mQM3GA
-        3xK43OMFrngO2fKQO8wRZIgydDIcIWaK4wwnOM1BDmc4zyEcEofuD692uKBp
-        AQAA
-        """
-
-    )
-
     // Simplified Scaffold.kt stubs
     private val ScaffoldStub = compiledStub(
         filename = "Scaffold.kt",
@@ -185,9 +153,9 @@
                 }
             """
             ),
-            PaddingValuesStub,
             ScaffoldStub,
             Stubs.Modifier,
+            Stubs.PaddingValues,
             Stubs.Composable
         )
             .run()
@@ -252,9 +220,9 @@
                 }
             """
             ),
-            PaddingValuesStub,
             ScaffoldStub,
             Stubs.Modifier,
+            Stubs.PaddingValues,
             Stubs.Composable
         )
             .run()
@@ -293,9 +261,9 @@
                 }
         """
             ),
-            PaddingValuesStub,
             ScaffoldStub,
             Stubs.Modifier,
+            Stubs.PaddingValues,
             Stubs.Composable
         )
             .run()
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt
index 731e868..3de524a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt
@@ -69,8 +69,7 @@
  * Alternatively, use Accompanist's [Flow Layouts](https://google.github.io/accompanist/flowlayout/)
  * to wrap chips to a new line.
  *
- * @param onClick called when the chip is clicked. If null, then this chip will be considered
- * read-only unless something else handles its input events and updates its state.
+ * @param onClick called when the chip is clicked.
  * @param modifier Modifier to be applied to the chip
  * @param enabled When disabled, chip will not respond to user input. It will also appear visually
  * disabled and disabled to accessibility services.
diff --git a/compose/material3/material3-lint/build.gradle b/compose/material3/material3-lint/build.gradle
new file mode 100644
index 0000000..c626c35
--- /dev/null
+++ b/compose/material3/material3-lint/build.gradle
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import androidx.build.BundleInsideHelper
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+}
+
+BundleInsideHelper.forInsideLintJar(project)
+
+dependencies {
+    compileOnly libs.androidLintMinComposeApi
+    compileOnly libs.kotlinStdlib
+    bundleInside(project(":compose:lint:common"))
+
+    testImplementation(project(":compose:lint:common-test"))
+    testImplementation libs.kotlinStdlib
+    testImplementation libs.androidLint
+    testImplementation libs.androidLintTests
+    testImplementation libs.junit
+    testImplementation libs.truth
+}
+
+androidx {
+    name = "Compose Material3 Lint Checks"
+    type = LibraryType.LINT
+    mavenGroup = LibraryGroups.COMPOSE_MATERIAL3
+    inceptionYear = "2022"
+    description = "Compose Material3 Lint Checks"
+}
diff --git a/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3IssueRegistry.kt b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3IssueRegistry.kt
new file mode 100644
index 0000000..8529446
--- /dev/null
+++ b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3IssueRegistry.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+
+/**
+ * [IssueRegistry] containing Material3 specific lint issues.
+ */
+class Material3IssueRegistry : IssueRegistry() {
+    // Tests are run with this version. We ensure that with ApiLintVersionsTest
+    override val api = 13
+    override val minApi = CURRENT_API
+    override val issues get() = listOf(
+        ScaffoldPaddingDetector.UnusedMaterial3ScaffoldPaddingParameter
+    )
+    override val vendor = Vendor(
+        vendorName = "Jetpack Compose",
+        identifier = "androidx.compose.material3",
+        feedbackUrl = "https://issuetracker.google.com/issues/new?component=612128"
+    )
+}
diff --git a/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3Names.kt b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3Names.kt
new file mode 100644
index 0000000..733fd2e
--- /dev/null
+++ b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3Names.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.lint
+
+import androidx.compose.lint.Package
+
+/**
+ * Contains common names used for Material3 lint checks.
+ */
+object Material3Names {
+    object Material3 {
+        val PackageName = Package("androidx.compose.material3")
+    }
+}
diff --git a/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/ScaffoldPaddingDetector.kt b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/ScaffoldPaddingDetector.kt
new file mode 100644
index 0000000..eba6ffd
--- /dev/null
+++ b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/ScaffoldPaddingDetector.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.lint
+
+import androidx.compose.lint.Name
+import androidx.compose.lint.findUnreferencedParameters
+import androidx.compose.lint.isInPackageName
+import androidx.compose.material3.lint.Material3Names.Material3
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.computeKotlinArgumentMapping
+import com.intellij.psi.PsiMethod
+import java.util.EnumSet
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.ULambdaExpression
+
+/**
+ * [Detector] that checks `Scaffold` usages for correctness.
+ *
+ * Scaffold provides an padding parameter to the `content` lambda. If this value is unused,
+ * then the content may be obscured by app bars defined by the scaffold.
+ */
+class ScaffoldPaddingDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableMethodNames(): List<String> = listOf(Scaffold.shortName)
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (method.isInPackageName(Material3.PackageName)) {
+            val contentArgument = computeKotlinArgumentMapping(node, method)
+                .orEmpty()
+                .filter { (_, parameter) ->
+                    parameter.name == "content"
+                }
+                .keys
+                .filterIsInstance<ULambdaExpression>()
+                .firstOrNull() ?: return
+
+            contentArgument.findUnreferencedParameters().forEach { unreferencedParameter ->
+                val location = unreferencedParameter.parameter
+                    ?.let { context.getLocation(it) }
+                    ?: context.getLocation(contentArgument)
+                val name = unreferencedParameter.name
+                context.report(
+                    UnusedMaterial3ScaffoldPaddingParameter,
+                    node,
+                    location,
+                    "Content padding parameter $name is not used"
+                )
+            }
+        }
+    }
+
+    companion object {
+        val UnusedMaterial3ScaffoldPaddingParameter = Issue.create(
+            "UnusedMaterial3ScaffoldPaddingParameter",
+            "Scaffold content should use the padding provided as a lambda parameter",
+            "The `content` lambda in Scaffold has a padding parameter " +
+                "which will include any inner padding for the content due to app bars. If this " +
+                "parameter is ignored, then content may be obscured by the app bars resulting in " +
+                "visual issues or elements that can't be interacted with.",
+            Category.CORRECTNESS, 3, Severity.ERROR,
+            Implementation(
+                ScaffoldPaddingDetector::class.java,
+                EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+            )
+        )
+    }
+}
+
+private val Scaffold = Name(Material3.PackageName, "Scaffold")
\ No newline at end of file
diff --git a/compose/material3/material3-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/compose/material3/material3-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
new file mode 100644
index 0000000..ffddc1d
--- /dev/null
+++ b/compose/material3/material3-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
@@ -0,0 +1 @@
+androidx.compose.material3.lint.Material3IssueRegistry
diff --git a/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ApiLintVersionsTest.kt b/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ApiLintVersionsTest.kt
new file mode 100644
index 0000000..6bb46ba
--- /dev/null
+++ b/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ApiLintVersionsTest.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.material3.lint
+
+import com.android.tools.lint.client.api.LintClient
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ApiLintVersionsTest {
+
+    @Test
+    fun versionsCheck() {
+        LintClient.clientName = LintClient.CLIENT_UNIT_TESTS
+
+        val registry = Material3IssueRegistry()
+        assertThat(registry.api).isEqualTo(CURRENT_API)
+        assertThat(registry.minApi).isEqualTo(10)
+    }
+}
diff --git a/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ScaffoldPaddingDetectorTest.kt b/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ScaffoldPaddingDetectorTest.kt
new file mode 100644
index 0000000..1fb1874
--- /dev/null
+++ b/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ScaffoldPaddingDetectorTest.kt
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.material3.lint
+
+import androidx.compose.lint.test.Stubs
+import androidx.compose.lint.test.compiledStub
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+
+/**
+ * Test for [ScaffoldPaddingDetector].
+ */
+class ScaffoldPaddingDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = ScaffoldPaddingDetector()
+
+    override fun getIssues(): MutableList<Issue> =
+        mutableListOf(ScaffoldPaddingDetector.UnusedMaterial3ScaffoldPaddingParameter)
+
+    // Simplified Scaffold.kt stubs
+    private val ScaffoldStub = compiledStub(
+        filename = "Scaffold.kt",
+        filepath = "androidx/compose/material3",
+        checksum = 0xfee46355,
+        """
+            package androidx.compose.material3
+
+            import androidx.compose.foundation.layout.PaddingValues
+            import androidx.compose.runtime.Composable
+            import androidx.compose.ui.Modifier
+
+            @Composable
+            fun Scaffold(
+                modifier: Modifier = Modifier,
+                topBar: @Composable () -> Unit = {},
+                bottomBar: @Composable () -> Unit = {},
+                content: @Composable (PaddingValues) -> Unit
+            ) {}
+
+        """,
+        """
+        META-INF/main.kotlin_module:
+        H4sIAAAAAAAAAGNgYGBmYGBgBGI2Bijg0uCSSsxLKcrPTKnQS87PLcgvTtXL
+        TSxJLcpMzDEW4gpOTkxLy89J8S7h4uViTsvPF2ILSS0u8S5RYtBiAACpks1u
+        UQAAAA==
+        """,
+        """
+        androidx/compose/material3/ScaffoldKt$Scaffold$1.class:
+        H4sIAAAAAAAAAKVU604TQRT+Zlt62RZbEOUi3kFbULYt3ktICIG4oWBisYnh
+        17S7haG7s6a7bfAfD+ET+ASiiSSaGOJPH8p4ZmkNhogaN9mzJ+d835lzm/32
+        /dMXAPfwhKHApdX2hLVnNDz3lefbhssDuy24M29UG7zZ9BxrLZjqq1PFOBjD
+        WqXlBY6Qxm7XNYQkguSOUeFu3eLlk75mRzYC4UnfWO1phYW+/4UUQXmxzDDx
+        +2BxRBmunB0wjhhDbEFQuEWGSC5fY4jmzHwtjQR0HQNIkSHYET5DqfKv9VJ+
+        MSG7XstmGMnlK7u8yw2Hy23jWX3XbgTlNDJI6tAwxJA6UVoc5xkS5kZ1c2lj
+        eYVh8Je607iAi0mMYJRACw0nzF4lHIaaUO5zSdImGYb6xHU74BYPOKWkud0I
+        jZApEVMCDKyllAg594TSCqRZRYbJo/2EfrSva1mNPtmj/QmtwJ7qX9/GtISm
+        MCVKfIFLT752vY5PPaRg03/VpzjuMGR/Nsuym7zjBAxvcqf73BHGumeJprDb
+        f1qR//QXy+bpMal1mINBpfbTnWtRptFlz6LJDle8BndqnAqsO/amEgyZipD2
+        Rset2+2eJW1KabeXHe77Nm1TZkU2HM8XcptGs+NZDMmq2JY86LQJrFe9Trth
+        rwrFHH/ekYFw7ZrwBYVaktILeJg2CjTmAWo53SyMq7nT7KL00i6QpUTaFCGY
+        mvRM5BDpg3Da8yTTx1YMhpwhtYg9xmyIoVeBNbrqCqYMqRNEdkzMLhEx2yOW
+        1CKpw2c+Yvg9xt6dwU/0Dk5Q2v2DRwmtntRnaC8PcekDLh+EhgHcJ6kT7Bgw
+        hgdhnXep/ofhIRE8Cr9FPA7/TnTxiXV1CxET10xcN3EDN01qxrSJW7i9BeYj
+        hzz5fcz4mPWR+QHL6C/y2gQAAA==
+        """,
+        """
+        androidx/compose/material3/ScaffoldKt$Scaffold$2.class:
+        H4sIAAAAAAAAAKVUbU/TUBR+bjf2xnADUV7Ed9ANlI7h+wgJIRAbBiYOlxg+
+        3bUdXNbemrVd8Bs/wl/gLxBNNNHEED/6o4znlk0xRNTYpKcn5zzPueft9uu3
+        j58B3MEjhhKXVtsT1p5ueu4Lz7d1lwd2W3BnXq+ZvNn0HGstmOypk+UkGMNa
+        teUFjpD6bsfVhSSC5I5e5W7D4pXjvmYozUB40tdXu1ppoed/JkVQWawwjP8+
+        WBJxhkunB0wiwZBYEBRukSFWKNYZ4gWjWM8ihUwGfegnQ7AjfIZy9V/rpfwS
+        Qna8ls0wXChWd3mH6w6X2/qTxq5tBpUsckhnoGGQof9YaUmcZUgZG7XNpY3l
+        FYaBX+rO4hzOpzGMEQItmE6UvUo4CjWu3GfSpE0wDPaI63bALR5wSklzOzEa
+        IVMioQQYWEspMXLuCaWVSLPmGCYO91OZw/2Mltfokz/cH9dK7HHmy+uEltIU
+        pkyJL3DpyZeuF/rUQwo29Vd9SuIWQ/5Hsyy7yUMnYHhVONnnUOjrniWawm7/
+        aUX+0z9XMU6OSa3DLHQqtZfubIsyjS97Fk12qOqZ3KlzKrDh2JtKMOSqQtob
+        oduw211L1pDSbi873Pdt2qbcijQdzxdym0az41kM6ZrYljwI2wTO1Lywbdqr
+        QjHHnoYyEK5dF76gUEtSegGP0kaJxtxHLaebhTE1d5pdnF7aBbKUSZskBFOT
+        no59QPYgmvY8yeyRFQMRZ1AtYpcxE2HoVWCNrrqCsYjyk8iOiPklIua7xLJa
+        JHX49HsMvcXom1P4qe7BKUq7d/AIodXT/wna8w+48A4XDyJDH+6SzBDsCDCK
+        e1Gdt6n++9EhMTyIvnN4GP2d6OIT6/IWYgauGLhq4BquG9SMKQM3cHMLzEcB
+        RfL7mPYx4yP3HfQNQiHaBAAA
+        """,
+        """
+        androidx/compose/material3/ScaffoldKt.class:
+        H4sIAAAAAAAAAMVUS3PbVBT+ru1YkmOnrhKniVtCaRya5lHZbnk6FFLTtCK2
+        6eA2m6yuZdkolq8yemTKhgnDX2DDln8Aqw4LxsOSf8EfYXok2yGNOwm0zLDQ
+        Pc97zneOzj1//PXrbwDuos6wwkXbdaz2M81w+oeOZ2p97puuxe07WtPgnY5j
+        t3d9CYwhe8CPuGZz0dW+bB2YBmnjDPLYi+G71dpEtMDS6k7b6limW6n1HN+2
+        hHZw1Nc6gTB8yxGetjPiim9oL1Vu7TH8+WYYtsb2p8LyK/f+W/fS1uYkuI4T
+        iDYPzdTab5zA1x7zdtsS3T1uB6ZXOZMhrHFlMoobCN/qm1o1knnLNisMyzXH
+        7WoHpt9yuUU4uBCOz4eYGo7fCGybvOT+qDcyUgxLpyqwBE2C4LamC9+lAJbh
+        SUgz5IyvTaM3ivCYu7xvkiPDzdXa2RGpnNI0wyBdqiCNGVxKIYMsQ9J3Du9z
+        yq0yKC3H951+JM4xSIZDAIQvY55wnf9bGa5fND0XupTIJTue5kLb7PDA9hl+
+        +J+nWp9sajgE184DJeEtamc4DFxQEIbzayiceFbSeBvXFSzhHYbiP9oNhZOW
+        lSQs0zzpjeaT7Ub1AUN5MusFESj/Ct5VUMDNl2fxFZ2TcOvfYyxLWH8NYOUI
+        2KaCDdxOYwrJFGIoMlwe/7266XN6xpxmKNY/itN6ZeGRDA8wsF7IxMj4zAo5
+        uhprlxi+HxzfSA2OU7FsLCILJyT65NiYzz/NDo7zsSIryzI5ExcvzxKXyGfU
+        hEr64tTvPyVjcjLSShPaK1k5PxvpUiOLMrQ8kkIoZRaiVMfVnH41E8rwnbyi
+        gRctMobpcTtv9+hdJapO22S4VLOE2Qj6LdN9Eu6tMKFjcHuP098geaRUmlZX
+        cD9wib/61XDb6eLI8iwyb/+92BgKZ60nG+olt0zT50avzg9HCdK6EKZbtbnn
+        mWRONZ3ANcwdK7QtjkLuTaRDieYgEf5joovhYJBUJYmP9Itr6vRzXF5XZ+nc
+        UHN0bqpX6Pw5uvJ5OCPU+QVaiQ+IXxteQoo0iDiVPhZxc/TFIm4eecSxE0WQ
+        8HAUQyb6KLQnSFCisTtzZhVcxTXiQ4R9SpUkWs4lEt/+iNQvuDHA0m4uMTWU
+        VgdYq+US0lDSSKqvrW9sPkdpCF2ncwrxmUwmqmKJkICSSIR9hmgO05RKwTLS
+        VJVCeL8gu0oXC1FlC/SUhnQ3CncfNaI1AlemsHf2EddxV8d7Ot7HBzo+xEc6
+        PkZlH8zDFj7Zx7SHKQ/3PKQ8LHhQPXzqQfYw52Hew2cetl8ABZTb8egIAAA=
+        """
+    )
+
+    @Test
+    fun unreferencedParameters() {
+        lint().files(
+            kotlin(
+                """
+                package foo
+
+                import androidx.compose.material3.*
+                import androidx.compose.runtime.*
+                import androidx.compose.ui.*
+
+                @Composable
+                fun Test() {
+                    Scaffold { /**/ }
+                    Scaffold(Modifier) { /**/ }
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}) { /**/ }
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}, content = { /**/ })
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}) { _ -> /**/ }
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}) { innerPadding -> /**/ }
+                }
+            """
+            ),
+            ScaffoldStub,
+            Stubs.Modifier,
+            Stubs.PaddingValues,
+            Stubs.Composable
+        )
+            .run()
+            .expect(
+                """
+src/foo/test.kt:10: Error: Content padding parameter it is not used [UnusedMaterial3ScaffoldPaddingParameter]
+                    Scaffold { /**/ }
+                             ~~~~~~~~
+src/foo/test.kt:11: Error: Content padding parameter it is not used [UnusedMaterial3ScaffoldPaddingParameter]
+                    Scaffold(Modifier) { /**/ }
+                                       ~~~~~~~~
+src/foo/test.kt:12: Error: Content padding parameter it is not used [UnusedMaterial3ScaffoldPaddingParameter]
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}) { /**/ }
+                                                                    ~~~~~~~~
+src/foo/test.kt:13: Error: Content padding parameter it is not used [UnusedMaterial3ScaffoldPaddingParameter]
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}, content = { /**/ })
+                                                                              ~~~~~~~~
+src/foo/test.kt:14: Error: Content padding parameter _ is not used [UnusedMaterial3ScaffoldPaddingParameter]
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}) { _ -> /**/ }
+                                                                      ~
+src/foo/test.kt:15: Error: Content padding parameter innerPadding is not used [UnusedMaterial3ScaffoldPaddingParameter]
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}) { innerPadding -> /**/ }
+                                                                      ~~~~~~~~~~~~
+6 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun unreferencedParameter_shadowedNames() {
+        lint().files(
+            kotlin(
+                """
+                package foo
+
+                import androidx.compose.material3.*
+                import androidx.compose.runtime.*
+                import androidx.compose.ui.*
+
+                val foo = false
+
+                @Composable
+                fun Test() {
+                    Scaffold {
+                        foo.let {
+                            // These `it`s refer to the `let`, not the `Scaffold`, so we
+                            // should still report an error
+                            it.let {
+                                if (it) { /**/ } else { /**/ }
+                            }
+                        }
+                    }
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}) { innerPadding ->
+                        foo.let { innerPadding ->
+                            // These `innerPadding`s refer to the `let`, not the `Scaffold`, so we
+                            // should still report an error
+                            innerPadding.let {
+                                if (innerPadding) { /**/ } else { /**/ }
+                            }
+                        }
+                    }
+                }
+            """
+            ),
+            ScaffoldStub,
+            Stubs.Modifier,
+            Stubs.PaddingValues,
+            Stubs.Composable
+        )
+            .run()
+            .expect(
+                """
+src/foo/test.kt:12: Error: Content padding parameter it is not used [UnusedMaterial3ScaffoldPaddingParameter]
+                    Scaffold {
+                             ^
+src/foo/test.kt:21: Error: Content padding parameter innerPadding is not used [UnusedMaterial3ScaffoldPaddingParameter]
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}) { innerPadding ->
+                                                                      ~~~~~~~~~~~~
+2 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun noErrors() {
+        lint().files(
+            kotlin(
+                """
+                package foo
+
+                import androidx.compose.material3.*
+                import androidx.compose.runtime.*
+                import androidx.compose.ui.*
+
+                @Composable
+                fun Test() {
+                    Scaffold {
+                        it
+                    }
+                    Scaffold(Modifier, topBar = {}, bottomBar = {}) { innerPadding ->
+                        innerPadding
+                    }
+                }
+        """
+            ),
+            ScaffoldStub,
+            Stubs.Modifier,
+            Stubs.PaddingValues,
+            Stubs.Composable
+        )
+            .run()
+            .expectClean()
+    }
+}
+/* ktlint-enable max-line-length */
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index 80a15e0..1a20d20 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -68,6 +68,8 @@
         androidTestImplementation(libs.mockitoCore)
         androidTestImplementation(libs.mockitoKotlin)
         androidTestImplementation(libs.testUiautomator)
+
+        lintPublish project(":compose:material3:material3-lint")
     }
 }
 
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/UnrememberedMutableStateDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/UnrememberedMutableStateDetector.kt
index 1310a6c..0a820be 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/UnrememberedMutableStateDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/UnrememberedMutableStateDetector.kt
@@ -20,7 +20,7 @@
 
 import androidx.compose.lint.Names
 import androidx.compose.lint.isInPackageName
-import androidx.compose.lint.invokedInComposableBodyAndNotRemembered
+import androidx.compose.lint.isNotRemembered
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Implementation
@@ -47,7 +47,7 @@
     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
         if (!method.isInPackageName(Names.Runtime.PackageName)) return
 
-        if (node.invokedInComposableBodyAndNotRemembered()) {
+        if (node.isNotRemembered()) {
             context.report(
                 UnrememberedMutableState,
                 node,
diff --git a/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt b/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt
index 7170fd85..29dd6fc 100644
--- a/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt
+++ b/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Bundle
 import androidx.activity.ComponentActivity
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -292,6 +293,29 @@
         }
     }
 
+    @Test
+    fun saveNothingWhenNoRememberSaveableIsUsedInternally() {
+        var showFirstPage by mutableStateOf(true)
+        val registry = SaveableStateRegistry(null) { true }
+
+        rule.setContent {
+            CompositionLocalProvider(LocalSaveableStateRegistry provides registry) {
+                val holder = rememberSaveableStateHolder()
+                holder.SaveableStateProvider(showFirstPage) {
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            showFirstPage = false
+        }
+
+        rule.runOnIdle {
+            val savedData = registry.performSave()
+            assertThat(savedData).isEqualTo(emptyMap<String, List<Any?>>())
+        }
+    }
+
     class Activity : ComponentActivity() {
         fun doFakeSave() {
             onSaveInstanceState(Bundle())
diff --git a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
index 30224a5..a34e0bf 100644
--- a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
+++ b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
@@ -97,10 +97,10 @@
         }
     }
 
-    private fun saveAll(): MutableMap<Any, Map<String, List<Any?>>> {
+    private fun saveAll(): MutableMap<Any, Map<String, List<Any?>>>? {
         val map = savedStates.toMutableMap()
         registryHolders.values.forEach { it.saveTo(map) }
-        return map
+        return map.ifEmpty { null }
     }
 
     override fun removeState(key: Any) {
@@ -122,7 +122,12 @@
 
         fun saveTo(map: MutableMap<Any, Map<String, List<Any?>>>) {
             if (shouldSave) {
-                map[key] = registry.performSave()
+                val savedData = registry.performSave()
+                if (savedData.isEmpty()) {
+                    map -= key
+                } else {
+                    map[key] = savedData
+                }
             }
         }
     }
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
index 7eb7645..75198d6 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
@@ -884,22 +884,12 @@
         )
         validate(create("style", style)) {
             parameter("style", ParameterType.String, TextStyle::class.java.simpleName) {
-                parameter("background", ParameterType.String, "Unspecified")
-                parameter("color", ParameterType.Color, Color.Red.toArgb(), index = 2)
-                parameter("fontSize", ParameterType.String, "Unspecified", index = 5)
-                parameter("letterSpacing", ParameterType.String, "Unspecified", index = 9)
-                parameter("lineHeight", ParameterType.String, "Unspecified", index = 10)
-                parameter("paragraphStyle", ParameterType.String, "ParagraphStyle", index = 13) {
-                    parameter("lineHeight", ParameterType.String, "Unspecified", index = 0)
-                }
-                parameter("spanStyle", ParameterType.String, "SpanStyle", index = 16) {
-                    parameter("background", ParameterType.String, "Unspecified")
-                    parameter("color", ParameterType.Color, Color.Red.toArgb(), index = 2)
-                    parameter("fontSize", ParameterType.String, "Unspecified", index = 5)
-                    parameter("letterSpacing", ParameterType.String, "Unspecified", index = 9)
-                    parameter("textDecoration", ParameterType.String, "Underline", index = 13)
-                }
-                parameter("textDecoration", ParameterType.String, "Underline", index = 18)
+                parameter("color", ParameterType.Color, Color.Red.toArgb())
+                parameter("fontSize", ParameterType.String, "Unspecified", index = 1)
+                parameter("letterSpacing", ParameterType.String, "Unspecified", index = 7)
+                parameter("background", ParameterType.String, "Unspecified", index = 11)
+                parameter("textDecoration", ParameterType.String, "Underline", index = 12)
+                parameter("lineHeight", ParameterType.String, "Unspecified", index = 15)
             }
         }
     }
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
index 984fa4a..752ff77 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
@@ -33,6 +33,7 @@
 import androidx.compose.ui.platform.InspectableModifier
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontListFontFamily
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
@@ -423,6 +424,14 @@
             return createReferenceToExistingValue(name, value, parentValue, existing)
         }
 
+        private fun create(
+            name: String,
+            value: Any?,
+            parentValue: Any?,
+            specifiedIndex: Int = 0
+        ): NodeParameter? =
+            create(name, value, parentValue)?.apply { index = specifiedIndex }
+
         private fun createFromSimpleValue(name: String, value: Any?): NodeParameter? {
             if (value == null) {
                 return null
@@ -474,6 +483,7 @@
             value.javaClass.isArray -> createFromArray(name, value, startIndex, maxElements)
             value is Offset -> createFromOffset(name, value)
             value is Shadow -> createFromShadow(name, value)
+            value is TextStyle -> createFromTextStyle(name, value)
             else -> createFromKotlinReflection(name, value)
         }
 
@@ -488,6 +498,7 @@
             value.javaClass.isArray -> findFromArray(value, index)
             value is Offset -> findFromOffset(value, index)
             value is Shadow -> findFromShadow(value, index)
+            value is TextStyle -> findFromTextStyle(value, index)
             else -> findFromKotlinReflection(value, index)
         }
 
@@ -912,6 +923,58 @@
             return Pair("blurRadius", with(density) { value.blurRadius.toDp() })
         }
 
+        // Temporary handling of TextStyle: remove when TextStyle implements InspectableValue
+        // Hide: paragraphStyle, spanStyle, platformStyle, lineHeightBehavior
+        private fun createFromTextStyle(name: String, value: TextStyle): NodeParameter? {
+            val parameter =
+                NodeParameter(name, ParameterType.String, TextStyle::class.java.simpleName)
+            val elements = parameter.elements
+            create("color", value.color, value)?.let { elements.add(it) }
+            create("fontSize", value.fontSize, value, 1)?.let { elements.add(it) }
+            create("fontWeight", value.fontWeight, value, 2)?.let { elements.add(it) }
+            create("fontStyle", value.fontStyle, value, 3)?.let { elements.add(it) }
+            create("fontSynthesis", value.fontSynthesis, value, 4)?.let { elements.add(it) }
+            create("fontFamily", value.fontFamily, value, 5)?.let { elements.add(it) }
+            create("fontFeatureSettings", value.fontFeatureSettings, value, 6)?.let {
+                elements.add(it)
+            }
+            create("letterSpacing", value.letterSpacing, value, 7)?.let { elements.add(it) }
+            create("baselineShift", value.baselineShift, value, 8)?.let { elements.add(it) }
+            create("textGeometricTransform", value.textGeometricTransform, value, 9)?.let {
+                elements.add(it)
+            }
+            create("localeList", value.localeList, value, 10)?.let { elements.add(it) }
+            create("background", value.background, value, 11)?.let { elements.add(it) }
+            create("textDecoration", value.textDecoration, value, 12)?.let { elements.add(it) }
+            create("shadow", value.shadow, value, 13)?.let { elements.add(it) }
+            create("textDirection", value.textDirection, value, 14)?.let { elements.add(it) }
+            create("lineHeight", value.lineHeight, value, 15)?.let { elements.add(it) }
+            create("textIndent", value.textIndent, value, 16)?.let { elements.add(it) }
+            return parameter
+        }
+
+        private fun findFromTextStyle(value: TextStyle, index: Int): Pair<String, Any?>? =
+            when (index) {
+                0 -> Pair("color", value.color)
+                1 -> Pair("fontSize", value.fontSize)
+                2 -> Pair("fontWeight", value.fontWeight)
+                3 -> Pair("fontStyle", value.fontStyle)
+                4 -> Pair("fontSynthesis", value.fontSynthesis)
+                5 -> Pair("fontFamily", value.fontFamily)
+                6 -> Pair("fontFeatureSettings", value.fontFeatureSettings)
+                7 -> Pair("letterSpacing", value.letterSpacing)
+                8 -> Pair("baselineShift", value.baselineShift)
+                9 -> Pair("textGeometricTransform", value.textGeometricTransform)
+                10 -> Pair("localeList", value.localeList)
+                11 -> Pair("background", value.background)
+                12 -> Pair("textDecoration", value.textDecoration)
+                13 -> Pair("shadow", value.shadow)
+                14 -> Pair("textDirection", value.textDirection)
+                15 -> Pair("lineHeight", value.lineHeight)
+                16 -> Pair("textIndent", value.textIndent)
+                else -> null
+            }
+
         @Suppress("DEPRECATION")
         private fun createFromTextUnit(name: String, value: TextUnit): NodeParameter =
             when (value.type) {
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index d1e7571..f203b26 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -113,6 +113,10 @@
 
   public final class ComposedModifierKt {
     method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, Object? key3, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object![]? keys, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
     method public static androidx.compose.ui.Modifier materialize(androidx.compose.runtime.Composer, androidx.compose.ui.Modifier modifier);
   }
 
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 1aebd5a..8cd62e2 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -113,10 +113,10 @@
 
   public final class ComposedModifierKt {
     method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
-    method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
-    method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
-    method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, Object? key3, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
-    method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object![]? keys, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, Object? key3, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object![]? keys, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
     method public static androidx.compose.ui.Modifier materialize(androidx.compose.runtime.Composer, androidx.compose.ui.Modifier modifier);
   }
 
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 9b0aade..1b85c50 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -113,6 +113,10 @@
 
   public final class ComposedModifierKt {
     method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, Object? key3, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+    method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object![]? keys, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
     method public static androidx.compose.ui.Modifier materialize(androidx.compose.runtime.Composer, androidx.compose.ui.Modifier modifier);
   }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt
index 51901fc..6bca085 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt
@@ -71,7 +71,6 @@
  * [materialize] must be called to create instance-specific modifiers if you are directly
  * applying a [Modifier] to an element tree node.
  */
-@ExperimentalComposeUiApi
 fun Modifier.composed(
     fullyQualifiedName: String,
     key1: Any?,
@@ -102,7 +101,6 @@
  * [materialize] must be called to create instance-specific modifiers if you are directly
  * applying a [Modifier] to an element tree node.
  */
-@ExperimentalComposeUiApi
 fun Modifier.composed(
     fullyQualifiedName: String,
     key1: Any?,
@@ -134,7 +132,6 @@
  * [materialize] must be called to create instance-specific modifiers if you are directly
  * applying a [Modifier] to an element tree node.
  */
-@ExperimentalComposeUiApi
 fun Modifier.composed(
     fullyQualifiedName: String,
     key1: Any?,
@@ -167,7 +164,6 @@
  * [materialize] must be called to create instance-specific modifiers if you are directly
  * applying a [Modifier] to an element tree node.
  */
-@ExperimentalComposeUiApi
 fun Modifier.composed(
     fullyQualifiedName: String,
     vararg keys: Any?,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocal.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocal.kt
index e82c42e..b98ef0a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocal.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocal.kt
@@ -24,7 +24,7 @@
  * current layout node. The provided [ModifierLocal]s are also available to layout nodes that are
  * children of the current layout node.
  *
- * One must create a [ModifierLocal] instance, which cah be referenced by consumers statically.
+ * One must create a [ModifierLocal] instance, which can be referenced by consumers statically.
  * [ModifierLocal] instances themselves hold no data, and can be thought of as a type-safe
  * identifier for the data being passed to other modifiers to the right of the providing
  * modifier or down the tree. [ModifierLocal] factory functions take a single parameter: a
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index db109a0..910a464 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -1084,6 +1084,11 @@
             <sha256 value="70cb1d3cc7f82c148e56c95a13255577bafbe343540ca74232ed8258e267d5e7" origin="Generated by Gradle because artifact wasn't signed"/>
          </artifact>
       </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-serialization" version="1.6.21">
+         <artifact name="kotlin-serialization-1.6.21.jar">
+            <sha256 value="da25598d1a5ab1a747fbdbbb61ac2f04cea7608fa01966d5712b7a93186cc225" origin="GradlePluginPortal artifact is missing the signature. see: https://youtrack.jetbrains.com/issue/KT-52162"/>
+         </artifact>
+      </component>
       <component group="org.jetbrains.kotlinx" name="atomicfu" version="0.17.0">
          <artifact name="atomicfu-metadata-0.17.0-all.jar">
             <sha256 value="b80d58fd737087f8a74d8ed802f5545e550209823e91d0062de0b09343f0c389" origin="Generated by Gradle because artifact wasn't signed"/>
diff --git a/health/health-connect-client/api/current.txt b/health/health-connect-client/api/current.txt
index fa18e8b..2b75ef7 100644
--- a/health/health-connect-client/api/current.txt
+++ b/health/health-connect-client/api/current.txt
@@ -333,6 +333,30 @@
   public static final class ElevationGained.Companion {
   }
 
+  public final class HeartRate {
+    ctor public HeartRate(java.time.Instant time, long beatsPerMinute);
+    method public long getBeatsPerMinute();
+    method public java.time.Instant getTime();
+    property public final long beatsPerMinute;
+    property public final java.time.Instant time;
+  }
+
+  public final class HeartRateSeries implements androidx.health.connect.client.records.Record {
+    ctor public HeartRateSeries(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List<androidx.health.connect.client.records.HeartRate> samples, optional androidx.health.connect.client.metadata.Metadata metadata);
+    method public java.time.Instant getEndTime();
+    method public java.time.ZoneOffset? getEndZoneOffset();
+    method public androidx.health.connect.client.metadata.Metadata getMetadata();
+    method public java.util.List<androidx.health.connect.client.records.HeartRate> getSamples();
+    method public java.time.Instant getStartTime();
+    method public java.time.ZoneOffset? getStartZoneOffset();
+    property public java.time.Instant endTime;
+    property public java.time.ZoneOffset? endZoneOffset;
+    property public androidx.health.connect.client.metadata.Metadata metadata;
+    property public java.util.List<androidx.health.connect.client.records.HeartRate> samples;
+    property public java.time.Instant startTime;
+    property public java.time.ZoneOffset? startZoneOffset;
+  }
+
   public final class Height implements androidx.health.connect.client.records.Record {
     ctor public Height(double heightMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.connect.client.metadata.Metadata metadata);
     method public double getHeightMeters();
diff --git a/health/health-connect-client/api/public_plus_experimental_current.txt b/health/health-connect-client/api/public_plus_experimental_current.txt
index fa18e8b..2b75ef7 100644
--- a/health/health-connect-client/api/public_plus_experimental_current.txt
+++ b/health/health-connect-client/api/public_plus_experimental_current.txt
@@ -333,6 +333,30 @@
   public static final class ElevationGained.Companion {
   }
 
+  public final class HeartRate {
+    ctor public HeartRate(java.time.Instant time, long beatsPerMinute);
+    method public long getBeatsPerMinute();
+    method public java.time.Instant getTime();
+    property public final long beatsPerMinute;
+    property public final java.time.Instant time;
+  }
+
+  public final class HeartRateSeries implements androidx.health.connect.client.records.Record {
+    ctor public HeartRateSeries(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List<androidx.health.connect.client.records.HeartRate> samples, optional androidx.health.connect.client.metadata.Metadata metadata);
+    method public java.time.Instant getEndTime();
+    method public java.time.ZoneOffset? getEndZoneOffset();
+    method public androidx.health.connect.client.metadata.Metadata getMetadata();
+    method public java.util.List<androidx.health.connect.client.records.HeartRate> getSamples();
+    method public java.time.Instant getStartTime();
+    method public java.time.ZoneOffset? getStartZoneOffset();
+    property public java.time.Instant endTime;
+    property public java.time.ZoneOffset? endZoneOffset;
+    property public androidx.health.connect.client.metadata.Metadata metadata;
+    property public java.util.List<androidx.health.connect.client.records.HeartRate> samples;
+    property public java.time.Instant startTime;
+    property public java.time.ZoneOffset? startZoneOffset;
+  }
+
   public final class Height implements androidx.health.connect.client.records.Record {
     ctor public Height(double heightMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.connect.client.metadata.Metadata metadata);
     method public double getHeightMeters();
diff --git a/health/health-connect-client/api/restricted_current.txt b/health/health-connect-client/api/restricted_current.txt
index 0b35453..e07f699 100644
--- a/health/health-connect-client/api/restricted_current.txt
+++ b/health/health-connect-client/api/restricted_current.txt
@@ -333,6 +333,30 @@
   public static final class ElevationGained.Companion {
   }
 
+  public final class HeartRate {
+    ctor public HeartRate(java.time.Instant time, long beatsPerMinute);
+    method public long getBeatsPerMinute();
+    method public java.time.Instant getTime();
+    property public final long beatsPerMinute;
+    property public final java.time.Instant time;
+  }
+
+  public final class HeartRateSeries implements androidx.health.connect.client.records.SeriesRecord<androidx.health.connect.client.records.HeartRate> {
+    ctor public HeartRateSeries(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List<androidx.health.connect.client.records.HeartRate> samples, optional androidx.health.connect.client.metadata.Metadata metadata);
+    method public java.time.Instant getEndTime();
+    method public java.time.ZoneOffset? getEndZoneOffset();
+    method public androidx.health.connect.client.metadata.Metadata getMetadata();
+    method public java.util.List<androidx.health.connect.client.records.HeartRate> getSamples();
+    method public java.time.Instant getStartTime();
+    method public java.time.ZoneOffset? getStartZoneOffset();
+    property public java.time.Instant endTime;
+    property public java.time.ZoneOffset? endZoneOffset;
+    property public androidx.health.connect.client.metadata.Metadata metadata;
+    property public java.util.List<androidx.health.connect.client.records.HeartRate> samples;
+    property public java.time.Instant startTime;
+    property public java.time.ZoneOffset? startZoneOffset;
+  }
+
   public final class Height implements androidx.health.connect.client.records.InstantaneousRecord {
     ctor public Height(double heightMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.connect.client.metadata.Metadata metadata);
     method public double getHeightMeters();
@@ -393,6 +417,11 @@
     property public abstract androidx.health.connect.client.metadata.Metadata metadata;
   }
 
+  @kotlin.PublishedApi internal interface SeriesRecord<T> extends androidx.health.connect.client.records.IntervalRecord {
+    method public java.util.List<T> getSamples();
+    property public abstract java.util.List<T> samples;
+  }
+
   public final class Steps implements androidx.health.connect.client.records.IntervalRecord {
     ctor public Steps(long count, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.connect.client.metadata.Metadata metadata);
     method public long getCount();
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateSeries.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateSeries.kt
index 981f549..95201d1 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateSeries.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateSeries.kt
@@ -15,14 +15,12 @@
  */
 package androidx.health.connect.client.records
 
-import androidx.annotation.RestrictTo
 import androidx.health.connect.client.aggregate.AggregateMetric
 import androidx.health.connect.client.metadata.Metadata
 import java.time.Instant
 import java.time.ZoneOffset
 
 /** Captures the user's heart rate. Each record represents a series of measurements. */
-@RestrictTo(RestrictTo.Scope.LIBRARY) // Will be made public after API reviews
 public class HeartRateSeries(
     override val startTime: Instant,
     override val startZoneOffset: ZoneOffset?,
@@ -100,7 +98,6 @@
  *
  * @param beatsPerMinute Heart beats per minute. Validation range: 1-300.
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY) // Will be made public after API reviews
 public class HeartRate(
     val time: Instant,
     val beatsPerMinute: Long,
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/SeriesRecord.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/SeriesRecord.kt
index 9fb8ee6..b98d9e5 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/SeriesRecord.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/SeriesRecord.kt
@@ -17,6 +17,7 @@
 
 /** A record that contains a series of measurements. */
 // Consider exposing this one and other similar interfaces!
+@PublishedApi
 internal interface SeriesRecord<out T : Any> : IntervalRecord {
 
     val samples: List<T>
diff --git a/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetector.kt b/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetector.kt
index 00a0b44..4313039 100644
--- a/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetector.kt
+++ b/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetector.kt
@@ -20,8 +20,8 @@
 
 import androidx.compose.lint.Name
 import androidx.compose.lint.Package
-import androidx.compose.lint.invokedInComposableBodyAndNotRemembered
 import androidx.compose.lint.isInPackageName
+import androidx.compose.lint.isNotRememberedWithKeys
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Implementation
@@ -31,12 +31,12 @@
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
 import com.intellij.psi.PsiMethod
-import org.jetbrains.uast.UCallExpression
 import java.util.EnumSet
+import org.jetbrains.uast.UCallExpression
 
 /**
  * [Detector] that checks `getBackStackEntry` calls to make sure that if they are called inside a
- * Composable body, they are `remember`ed.
+ * Composable body, they are `remember`ed with a `NavBackStackEntry` as a key.
  */
 class UnrememberedGetBackStackEntryDetector : Detector(), SourceCodeScanner {
     override fun getApplicableMethodNames(): List<String> = listOf(
@@ -46,12 +46,13 @@
     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
         if (!method.isInPackageName(PackageName)) return
 
-        if (node.invokedInComposableBodyAndNotRemembered()) {
+        if (node.isNotRememberedWithKeys(NavBackStackEntry)) {
             context.report(
                 UnrememberedGetBackStackEntry,
                 node,
                 context.getNameLocation(node),
-                "Calling getBackStackEntry during composition without using `remember`"
+                "Calling getBackStackEntry during composition without using `remember` " +
+                    "with a NavBackStackEntry key"
             )
         }
     }
@@ -59,11 +60,15 @@
     companion object {
         val UnrememberedGetBackStackEntry = Issue.create(
             "UnrememberedGetBackStackEntry",
-            "Calling getBackStackEntry during composition with using `remember`",
+            "Calling getBackStackEntry during composition without using `remember`" +
+                "with a NavBackStackEntry key",
             "Backstack entries retrieved during composition need to be `remember`ed, otherwise " +
-                "they will be retrieved from the navController again, and be changed. Either " +
-                "hoist the state to an object that is not created during composition, or wrap " +
-                "the state in a call to `remember`.",
+                "they will be retrieved from the navController again, and be changed. You also " +
+                "need to pass in a key of a NavBackStackEntry to the remember call or they will " +
+                "not be updated properly. If this is in a `NavGraphBuilder.composable` scope, " +
+                "you should pass in the lambda's given entry as the key. Either hoist the state " +
+                "to an object that is not created during composition, or wrap the state in a " +
+                "call to `remember` with a `NavBackStackEntry` as a key.",
             Category.CORRECTNESS, 3, Severity.ERROR,
             Implementation(
                 UnrememberedGetBackStackEntryDetector::class.java,
@@ -75,3 +80,4 @@
 
 private val PackageName = Package("androidx.navigation")
 private val GetBackStackEntry = Name(PackageName, "getBackStackEntry")
+private val NavBackStackEntry = Name(PackageName, "NavBackStackEntry")
diff --git a/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetectorTest.kt b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetectorTest.kt
index defabf5..b33210d 100644
--- a/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetectorTest.kt
+++ b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetectorTest.kt
@@ -108,28 +108,28 @@
             .run()
             .expect(
                 """
-src/com/example/{.kt:10: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:10: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
                     navController.getBackStackEntry("test")
                                   ~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:15: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:15: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
                     navController.getBackStackEntry("test")
                                   ~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:20: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:20: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
                     navController.getBackStackEntry("test")
                                   ~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:30: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:30: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
                         navController.getBackStackEntry("test")
                                       ~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:34: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:34: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
                         navController.getBackStackEntry("test")
                                       ~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:41: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:41: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
                         navController.getBackStackEntry("test")
                                       ~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:46: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:46: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
                         navController.getBackStackEntry("test")
                                       ~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:54: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:54: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
                         val entry = navController.getBackStackEntry("test")
                                                   ~~~~~~~~~~~~~~~~~
 8 errors, 0 warnings
@@ -138,7 +138,7 @@
     }
 
     @Test
-    fun rememberedInsideComposableBody() {
+    fun rememberedInsideComposableBodyWithoutEntryKey() {
         lint().files(
             kotlin(
                 """
@@ -196,6 +196,102 @@
             NAV_CONTROLLER
         )
             .run()
+            .expect(
+                """
+src/com/example/test.kt:10: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+                    val entry = remember { navController.getBackStackEntry("test") }
+                                                         ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:15: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+                    val entry = remember { navController.getBackStackEntry("test") }
+                                                         ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:20: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+                    val entry = remember { navController.getBackStackEntry("test") }
+                                                         ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:30: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+                        val entry = remember { navController.getBackStackEntry("test") }
+                                                             ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:34: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+                        val entry = remember { navController.getBackStackEntry("test") }
+                                                             ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:41: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+                        val entry = remember { navController.getBackStackEntry("test") }
+                                                             ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:46: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+                        val entry = remember { navController.getBackStackEntry("test") }
+                                                             ~~~~~~~~~~~~~~~~~
+7 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun rememberedInsideComposableBodyWithEntryKey() {
+        lint().files(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.compose.runtime.*
+                import androidx.navigation.NavController
+                import androidx.navigation.NavBackStackEntry
+
+                @Composable
+                fun Test() {
+                    val navController = NavController()
+                    val rememberedEntry = NavBackStackEntry()
+                    val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+                }
+
+                val lambda = @Composable {
+                    val navController = NavController()
+                    val rememberedEntry = NavBackStackEntry()
+                    val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+                }
+
+                val lambda2: @Composable () -> Unit = {
+                    val navController = NavController()
+                    val rememberedEntry = NavBackStackEntry()
+                    val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+                }
+
+                @Composable
+                fun LambdaParameter(content: @Composable () -> Unit) {}
+
+                @Composable
+                fun Test2() {
+                    LambdaParameter(content = {
+                        val navController = NavController()
+                        val rememberedEntry = NavBackStackEntry()
+                        val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+                    })
+                    LambdaParameter {
+                        val navController = NavController()
+                        val rememberedEntry = NavBackStackEntry()
+                        val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+                    }
+                }
+
+                fun test3() {
+                    val localLambda1 = @Composable {
+                        val navController = NavController()
+                        val rememberedEntry = NavBackStackEntry()
+                        val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+                    }
+
+                    val localLambda2: @Composable () -> Unit = {
+                        val navController = NavController()
+                        val rememberedEntry = NavBackStackEntry()
+                        val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+                    }
+                }
+            """
+            ),
+            Stubs.Composable,
+            Stubs.Remember,
+            NAV_BACK_STACK_ENTRY,
+            NAV_CONTROLLER
+        )
+            .run()
             .expectClean()
     }
 
diff --git a/navigation/navigation-compose/samples/build.gradle b/navigation/navigation-compose/samples/build.gradle
index 5f4d6a8..981abe6 100644
--- a/navigation/navigation-compose/samples/build.gradle
+++ b/navigation/navigation-compose/samples/build.gradle
@@ -47,3 +47,15 @@
 android {
     namespace "androidx.navigation.compose.samples"
 }
+
+// Workaround for https://github.com/gradle/gradle/issues/19882
+configurations.all {
+    resolutionStrategy.dependencySubstitution {
+        substitute(module("androidx.lifecycle:lifecycle-runtime:")).
+                using project(":lifecycle:lifecycle-runtime")
+        substitute(module("androidx.lifecycle:lifecycle-runtime-ktx:")).
+                using project(":lifecycle:lifecycle-runtime-ktx")
+        substitute(module("androidx.savedstate:savedstate-ktx:")).
+                using project(":savedstate:savedstate-ktx")
+    }
+}
diff --git a/navigation/navigation-runtime-lint/build.gradle b/navigation/navigation-runtime-lint/build.gradle
new file mode 100644
index 0000000..c586a3d
--- /dev/null
+++ b/navigation/navigation-runtime-lint/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+}
+
+dependencies {
+    compileOnly(libs.androidLintMinApi)
+    compileOnly(libs.kotlinStdlib)
+
+    testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.androidLint)
+    testImplementation(libs.androidLintTests)
+    testImplementation(libs.junit)
+    testImplementation(libs.truth)
+}
+
+androidx {
+    name = "Navigation Runtime Lint"
+    type = LibraryType.LINT
+    mavenGroup = LibraryGroups.NAVIGATION
+    inceptionYear = "2022"
+    description = "Lint checks for Navigation Runtime"
+}
diff --git a/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetector.kt b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetector.kt
new file mode 100644
index 0000000..89d6730
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetector.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.navigation.runtime.lint
+
+import com.android.SdkConstants.TAG_ACTIVITY
+import com.android.SdkConstants.TAG_DEEP_LINK
+import com.android.resources.ResourceFolderType
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.ResourceXmlDetector
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.XmlContext
+import java.util.Collections
+import org.w3c.dom.Element
+
+/**
+ * Lint check for detecting use of <deeplink> inside of <activity>.
+ */
+class DeepLinkInActivityDestinationDetector : ResourceXmlDetector() {
+
+    override fun appliesTo(folderType: ResourceFolderType): Boolean {
+        return folderType == ResourceFolderType.NAVIGATION
+    }
+
+    override fun getApplicableElements(): Collection<String>? = Collections.singleton(TAG_DEEP_LINK)
+
+    override fun visitElement(context: XmlContext, element: Element) {
+        if (element.parentNode?.nodeName == TAG_ACTIVITY) {
+            val incident = Incident(context)
+                .issue(DeepLinkInActivityDestination)
+                .location(context.getLocation(element))
+                .message("Do not attach a <deeplink> to an <activity> destination. " +
+                    "Attach the deeplink directly to the second activity or the start " +
+                    "destination of a nav host in the second activity instead.")
+                .scope(context.getNameLocation(element))
+            context.report(incident)
+        }
+    }
+
+    companion object {
+        val DeepLinkInActivityDestination = Issue.create(
+            id = "DeepLinkInActivityDestination",
+            briefDescription = "A <deeplink> should not be attached to an <activity> destination",
+            explanation = """Attaching a <deeplink> to an <activity> destination will never give \
+                the right behavior when using an implicit deep link on another app's task \
+                (where the system back should immediately take the user back to the app that \
+                triggered the deep link). Instead, attach the deep link directly to \
+                the second activity (either by manually writing the appropriate <intent-filter> \
+                or by adding the <deeplink> to the start destination of a nav host in that second \
+                activity).""",
+            category = Category.CORRECTNESS,
+            severity = Severity.WARNING,
+            implementation = Implementation(
+                DeepLinkInActivityDestinationDetector::class.java, Scope.RESOURCE_FILE_SCOPE
+            ),
+            androidSpecific = true
+        )
+    }
+}
diff --git a/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/NavigationRuntimeIssueRegistry.kt b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/NavigationRuntimeIssueRegistry.kt
new file mode 100644
index 0000000..b4d90ba
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/NavigationRuntimeIssueRegistry.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.navigation.runtime.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+
+/**
+ * [IssueRegistry] containing runtime specific lint issues.
+ */
+class NavigationRuntimeIssueRegistry : IssueRegistry() {
+    // Tests are run with this version. We ensure that with ApiLintVersionsTest
+    override val api = 13
+    override val minApi = CURRENT_API
+    override val issues get() = listOf(
+        DeepLinkInActivityDestinationDetector.DeepLinkInActivityDestination
+    )
+    override val vendor = Vendor(
+        feedbackUrl = "https://issuetracker.google.com/issues/new?component=409828",
+        vendorName = "Android Open Source Project",
+        identifier = "androidx.navigation.runtime"
+    )
+}
diff --git a/navigation/navigation-runtime-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/navigation/navigation-runtime-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
new file mode 100644
index 0000000..89f8da4
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
@@ -0,0 +1 @@
+androidx.navigation.runtime.lint.NavigationRuntimeIssueRegistry
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/ApiLintVersionsTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/ApiLintVersionsTest.kt
new file mode 100644
index 0000000..64f4740
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/ApiLintVersionsTest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.runtime.lint
+
+import com.android.tools.lint.client.api.LintClient
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ApiLintVersionsTest {
+    @Test
+    fun versionsCheck() {
+        LintClient.clientName = LintClient.CLIENT_UNIT_TESTS
+
+        val registry = NavigationRuntimeIssueRegistry()
+        assertThat(registry.api).isEqualTo(CURRENT_API)
+        assertThat(registry.minApi).isEqualTo(10)
+    }
+}
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetectorTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetectorTest.kt
new file mode 100644
index 0000000..a9e4177
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetectorTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.runtime.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class DeepLinkInActivityDestinationDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = DeepLinkInActivityDestinationDetector()
+
+    override fun getIssues(): MutableList<Issue> {
+        return mutableListOf(DeepLinkInActivityDestinationDetector.DeepLinkInActivityDestination)
+    }
+
+    @Test
+    fun expectPass() {
+        lint().files(
+            xml("res/navigation/nav_main.xml",
+                """
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/nav_main"
+    app:startDestination="@id/fragment_main"
+    >
+
+    <fragment
+        android:id="@+id/fragment_main"
+        android:name="com.example.deeplink.MainFragment"
+        >
+        <deepLink app:uri="www.example.com" />
+    </fragment>
+
+    <activity
+        android:id="@+id/activity_deep_link"
+        android:name="com.example.deeplink.Activity"
+        />
+
+</navigation>
+            """
+            )
+        )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun expectFail() {
+        lint().files(
+            xml("res/navigation/nav_main.xml",
+            """
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/nav_main"
+    app:startDestination="@id/fragment_main"
+    >
+
+    <fragment
+        android:id="@+id/fragment_main"
+        android:name="com.example.deeplink.MainFragment"
+        />
+
+    <activity
+        android:id="@+id/activity_deep_link"
+        android:name="com.example.deeplink.DeepLinkActivity"
+        >
+        <deepLink app:uri="www.example.com" />
+    </activity>
+
+</navigation>
+            """
+            )
+        )
+            .run()
+            .expect("""
+res/navigation/nav_main.xml:17: Warning: Do not attach a <deeplink> to an <activity> destination. Attach the deeplink directly to the second activity or the start destination of a nav host in the second activity instead. [DeepLinkInActivityDestination]
+        <deepLink app:uri="www.example.com" />
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+            )
+    }
+}
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index 34ea7a4..151198c6 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -47,6 +47,8 @@
     androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy)
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.multidex)
+
+    lintPublish(project(':navigation:navigation-runtime-lint'))
 }
 
 android {
@@ -69,4 +71,4 @@
         // Allow usage of Kotlin's @OptIn.
         freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn"]
     }
-}
\ No newline at end of file
+}
diff --git a/playground-common/androidx-shared.properties b/playground-common/androidx-shared.properties
index c31e4d0..d678753 100644
--- a/playground-common/androidx-shared.properties
+++ b/playground-common/androidx-shared.properties
@@ -25,21 +25,56 @@
 # at configuration time.
 
 org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true
+org.gradle.configureondemand=true
+org.gradle.parallel=true
+org.gradle.caching=true
+# Disabled due to https://github.com/gradle/gradle/issues/18626
+# org.gradle.vfs.watch=true
+org.gradle.dependency.verification.console=verbose
+org.gradle.unsafe.configuration-cache=true
+org.gradle.unsafe.configuration-cache-problems=warn
+org.gradle.unsafe.configuration-cache.max-problems=4000
+
+android.uniquePackageNames=false
+android.enableAdditionalTestOutput=true
 android.useAndroidX=true
+android.nonTransitiveRClass=true
+android.disableAutomaticComponentCreation=true
+# Suppress pointless warning about mpp being experimental
+kotlin.mpp.stability.nowarn=true
+# Workaround for b/141364941
+android.forceJacocoOutOfProcess=true
+android.experimental.lint.missingBaselineIsEmptyBaseline=true
+
+# Generate versioned API files
+androidx.writeVersionedApiFiles=true
+
 # Disable features we do not use
 android.defaults.buildfeatures.aidl=false
 android.defaults.buildfeatures.buildconfig=false
 android.defaults.buildfeatures.renderscript=false
 android.defaults.buildfeatures.resvalues=false
 android.defaults.buildfeatures.shaders=false
-android.disableAutomaticComponentCreation=true
-android.experimental.lint.missingBaselineIsEmptyBaseline=true
 
-org.gradle.configureondemand=true
-org.gradle.parallel=true
-org.gradle.caching=true
-# Disabled due to https://github.com/gradle/gradle/issues/18626
-# org.gradle.vfs.watch=true
-org.gradle.unsafe.configuration-cache=true
-org.gradle.unsafe.configuration-cache-problems=warn
-org.gradle.unsafe.configuration-cache.max-problems=4000
+# do not automatically include stdlib
+kotlin.stdlib.default.dependency=false
+
+# https://b.corp.google.com/issues/227307216
+kotlin.mpp.absentAndroidTarget.nowarn=true
+
+# Enable adding baseline-prof.txt files to AAR artifacts
+android.experimental.enableArtProfiles=true
+
+# Disallow resolving dependencies at configuration time, which is a slight performance problem
+android.dependencyResolutionAtConfigurationTime.disallow=true
+android.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarnings,android.dependencyResolutionAtConfigurationTime.disallow,android.experimental.lint.missingBaselineIsEmptyBaseline
+# Workaround for b/162074215
+android.includeDependencyInfoInApks=false
+
+# Properties we often want to toggle
+# ksp.version.check=false
+# androidx.compose.multiplatformEnabled=true
+
+# Do _not_ toggle or override unless you have read and understand this:
+# https://blog.jetbrains.com/kotlin/2021/10/important-ua-parser-js-exploit-and-kotlin-js/
+androidx.kmp.js.enabled=false
diff --git a/room/integration-tests/kotlintestapp/build.gradle b/room/integration-tests/kotlintestapp/build.gradle
index 128ef22..00e68bc 100644
--- a/room/integration-tests/kotlintestapp/build.gradle
+++ b/room/integration-tests/kotlintestapp/build.gradle
@@ -86,7 +86,6 @@
             project(path: ":room:room-compiler", configuration: "shadowAndImplementation")
     )
     androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-livedata-ktx"))
-    androidTestImplementation(projectOrArtifact(":arch:core:core-runtime")) // Added for b/155802460
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRunner) {
@@ -100,14 +99,13 @@
     androidTestImplementation(libs.truth)
     androidTestImplementation(libs.kotlinTest)
     androidTestImplementation(project(":room:room-guava"))
-    androidTestImplementation(project(":room:room-paging")) // Added for b/155802460
     androidTestImplementation(project(":room:room-testing"))
     androidTestImplementation(project(":room:room-rxjava2"))
     androidTestImplementation(project(":room:room-rxjava3"))
     androidTestImplementation(project(":room:room-ktx"))
     androidTestImplementation(project(":internal-testutils-common"))
     androidTestImplementation("androidx.arch.core:core-testing:2.0.1")
-    androidTestImplementation(projectOrArtifact(":paging:paging-runtime"))
+    androidTestImplementation("androidx.paging:paging-runtime:3.1.1")
     androidTestImplementation(libs.guavaAndroid)
     androidTestImplementation(libs.rxjava2)
     testImplementation(libs.mockitoCore)
diff --git a/room/room-paging/build.gradle b/room/room-paging/build.gradle
index 84367bf..e1a2e69 100644
--- a/room/room-paging/build.gradle
+++ b/room/room-paging/build.gradle
@@ -47,7 +47,7 @@
 
     api(project(":room:room-runtime"))
     implementation(project(":room:room-ktx"))
-    api("androidx.paging:paging-common:3.1.0")
+    api("androidx.paging:paging-common:3.1.1")
 
     androidTestImplementation(libs.kotlinCoroutinesTest)
     androidTestImplementation(libs.multidex)
diff --git a/settings.gradle b/settings.gradle
index 8bd3b85..afc7025 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -431,6 +431,7 @@
 includeProject(":compose:lint:common-test", [BuildType.COMPOSE, BuildType.MAIN])
 includeProject(":compose:material", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3", [BuildType.COMPOSE])
+includeProject(":compose:material3:material3-lint", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-window-size-class", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-window-size-class:material3-window-size-class-samples", "compose/material3/material3-window-size-class/samples", [BuildType.COMPOSE])
 includeProject(":compose:material:material", [BuildType.COMPOSE])
@@ -659,6 +660,7 @@
 includeProject(":navigation:navigation-integration-tests", "navigation/integration-tests", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":navigation:navigation-integration-tests:testapp", "navigation/integration-tests/testapp", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":navigation:navigation-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":navigation:navigation-runtime-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":navigation:navigation-runtime-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":navigation:navigation-runtime-truth", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":navigation:navigation-safe-args-generator", [BuildType.MAIN, BuildType.FLAN])
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
index 8a0ad6b..be31ccb 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
@@ -221,7 +221,7 @@
                         initiallySelectedOption = 6
                     ),
                     focusRequester = focusRequester1,
-                    modifier = Modifier.size(40.dp, 100.dp),
+                    modifier = Modifier.size(64.dp, 100.dp),
                     readOnlyLabel = { LabelText("Hour") }
                 ) { hour: Int ->
                     TimePiece(
diff --git a/wear/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/wear/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index dac72dd..e7975fc 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/wear/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -50,10 +50,6 @@
             android:theme="@style/AppTheme"
             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-            <intent-filter>
                 <action android:name=
                     "androidx.wear.compose.integration.macrobenchmark.target.SWIPE_ACTIVITY" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -76,6 +72,10 @@
             android:theme="@style/AppTheme"
             android:exported="true">
             <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
                 <action android:name=
                     "androidx.wear.compose.integration.macrobenchmark.target.BASELINE_ACTIVITY" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt b/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
index 756bf60..84a58aa 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
+++ b/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
@@ -19,11 +19,15 @@
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
+import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.rememberScrollState
@@ -83,6 +87,14 @@
 
 private val ALERT_DIALOG = "alert-dialog"
 private val CONFIRMATION_DIALOG = "confirmation-dialog"
+private val BUTTONS = "buttons"
+private val CARDS = "cards"
+private val CHIPS = "chips"
+private val DIALOGS = "dialogs"
+private val PICKER = "picker"
+private val PROGRESSINDICATORS = "progressindicators"
+private val SLIDER = "slider"
+private val START_INDEX = "start-index"
 private val STEPPER = "stepper"
 private val SWIPE_DISMISS = "swipe-dismiss"
 private val PROGRESS_INDICATOR = "progress-indicator"
@@ -101,39 +113,18 @@
                 Scaffold(
                     timeText = { TimeText() },
                     positionIndicator = { PositionIndicator(scrollState = scrollState) },
-                    vignette = {
-                        Vignette(vignettePosition = VignettePosition.TopAndBottom)
-                    },
+                    vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) },
                 ) {
                     SwipeDismissableNavHost(
                         navController = navController,
-                        startDestination = "start",
+                        startDestination = START_INDEX,
                         modifier = Modifier
                             .background(MaterialTheme.colors.background)
                             .semantics { contentDescription = SWIPE_DISMISS }
                     ) {
-                        composable("start") {
-                            Box {
-                                CurvedTexts()
-                                Column(
-                                    modifier = Modifier
-                                        .fillMaxSize()
-                                        .verticalScroll(state = scrollState)
-                                        .padding(vertical = 16.dp)
-                                        .semantics { contentDescription = CONTENT_DESCRIPTION },
-                                    verticalArrangement = Arrangement.Center,
-                                    horizontalAlignment = Alignment.CenterHorizontally
-                                ) {
-                                    Dialogs(navController)
-                                    Steppers(navController)
-                                    ProgressIndicators(navController)
-                                    Buttons()
-                                    Cards()
-                                    Chips()
-                                    Sliders()
-                                    Pickers()
-                                }
-                            }
+                        composable(START_INDEX) { StartIndex(navController, scrollState) }
+                        composable(DIALOGS) {
+                            Dialogs(navController)
                         }
                         composable(ALERT_DIALOG) {
                             Alert(
@@ -148,17 +139,11 @@
                                 content = { Text("Confirmation") },
                             )
                         }
-                        composable(STEPPER) {
-                            var value by remember { mutableStateOf(2f) }
-                            Stepper(
-                                value = value,
-                                onValueChange = { value = it },
-                                increaseIcon = { Icon(StepperDefaults.Increase, "Increase") },
-                                decreaseIcon = { Icon(StepperDefaults.Decrease, "Decrease") },
-                                valueRange = 1f..4f,
-                                steps = 7
-                            ) { Text("Value: $value") }
-                        }
+                        composable(BUTTONS) { Buttons() }
+                        composable(CARDS) { Cards() }
+                        composable(CHIPS) { Chips() }
+                        composable(PICKER) { Picker(scrollState) }
+                        composable(PROGRESSINDICATORS) { ProgressIndicators(navController) }
                         composable(PROGRESS_INDICATOR) {
                             CircularProgressIndicator(
                                 modifier = Modifier.fillMaxSize(),
@@ -170,11 +155,14 @@
                         composable(PROGRESS_INDICATOR_INDETERMINATE) {
                             Column(
                                 verticalArrangement = Arrangement.Center,
-                                horizontalAlignment = Alignment.CenterHorizontally
+                                horizontalAlignment = Alignment.CenterHorizontally,
+                                modifier = Modifier.fillMaxSize(),
                             ) {
                                 CircularProgressIndicator()
                             }
                         }
+                        composable(SLIDER) { Slider() }
+                        composable(STEPPER) { Stepper() }
                     }
                 }
             }
@@ -183,49 +171,234 @@
 }
 
 @Composable
+fun StartIndex(navController: NavHostController, scrollState: ScrollState) {
+    Box {
+        CurvedTexts()
+        Column(
+            modifier = Modifier
+                .fillMaxSize()
+                .verticalScroll(state = scrollState)
+                .padding(vertical = 32.dp)
+                .semantics { contentDescription = CONTENT_DESCRIPTION },
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+                Widget(navController, BUTTONS, "Btn", BUTTONS)
+                Widget(navController, CARDS, "Card", CARDS)
+            }
+            Spacer(modifier = Modifier.height(4.dp))
+            Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+                Widget(navController, CHIPS, "Chip", CHIPS)
+                Widget(navController, DIALOGS, "Dlg", DIALOGS)
+                Widget(navController, PICKER, "Pick", PICKER)
+            }
+            Spacer(modifier = Modifier.height(4.dp))
+            Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+                Widget(navController, PROGRESSINDICATORS,
+                    "Prog", PROGRESSINDICATORS)
+                Widget(navController, SLIDER, "Sldr", SLIDER)
+                Widget(navController, STEPPER, "Stpr", STEPPER)
+            }
+        }
+    }
+}
+
+@Composable
+fun Dialogs(navController: NavHostController) {
+    Column(
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        ListHeader { Text("Dialogs") }
+        CompactChip(
+            onClick = { navController.navigate(ALERT_DIALOG) },
+            colors = ChipDefaults.primaryChipColors(),
+            label = { Text(ALERT_DIALOG) },
+            modifier = Modifier.semantics {
+                contentDescription = ALERT_DIALOG
+            },
+        )
+        Spacer(Modifier.height(4.dp))
+        CompactChip(
+            onClick = { navController.navigate(CONFIRMATION_DIALOG) },
+            colors = ChipDefaults.primaryChipColors(),
+            label = { Text(CONFIRMATION_DIALOG) },
+            modifier = Modifier.semantics {
+                contentDescription = CONFIRMATION_DIALOG
+            },
+        )
+    }
+}
+
+@Composable
 fun Buttons() {
-    ListHeader { Text("Buttons") }
-    Button(onClick = {}) { Text("Button") }
-    CompactButton(onClick = {}) { Text("CompactButton") }
-    ToggleButton(checked = true, onCheckedChange = {}) { Text("ToggleButton") }
+    Column(
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        ListHeader { Text("Buttons") }
+        Button(onClick = {}) { Text("Button") }
+        CompactButton(onClick = {}) { Text("CompactButton") }
+        ToggleButton(
+            checked = true,
+            onCheckedChange = {}) { Text("ToggleButton") }
+    }
 }
 
 @Composable
 fun Cards() {
-    ListHeader { Text("Cards") }
-    Card(onClick = {}) { Text("Card") }
-    AppCard(onClick = {}, appName = {}, time = {}, title = {}) { Text("AppCard") }
-    TitleCard(onClick = {}, title = {}) { Text("TitleCard") }
+    Column(
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        ListHeader { Text("Cards") }
+        Card(onClick = {}) { Text("Card") }
+        AppCard(onClick = {},
+            appName = { Text("AppName") }, title = {},
+            time = { Text("02:34") }) {
+            Text("AppCard")
+        }
+        TitleCard(onClick = {}, title = { Text("Title") }) {
+            Text("TitleCard")
+        }
+    }
 }
 
 @Composable
 fun Chips() {
-    ListHeader { Text("Chips") }
-    Chip(onClick = {}, colors = ChipDefaults.primaryChipColors()) { Text("Chip") }
-    CompactChip(onClick = {}, label = { Text("CompactChip") })
-    ToggleChip(
-        checked = true,
-        onCheckedChange = {},
-        label = { Text("ToggleChip") },
-        toggleControl = {
-            Icon(
-                imageVector = ToggleChipDefaults.radioIcon(checked = false),
-                contentDescription = null
-            )
-        }
-    )
-    SplitToggleChip(
-        checked = true,
-        onCheckedChange = {},
-        label = { Text("SplitToggleChip") },
-        onClick = {},
-        toggleControl = {
-            Icon(
-                imageVector = ToggleChipDefaults.radioIcon(checked = true),
-                contentDescription = null
-            )
-        }
-    )
+    Column(
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        ListHeader { Text("Chips") }
+        Chip(
+            onClick = {},
+            colors = ChipDefaults.primaryChipColors()
+        ) { Text("Chip") }
+        CompactChip(onClick = {}, label = { Text("CompactChip") })
+        ToggleChip(
+            checked = true,
+            onCheckedChange = {},
+            label = { Text("ToggleChip") },
+            toggleControl = {
+                Icon(
+                    imageVector =
+                    ToggleChipDefaults.radioIcon(checked = false),
+                    contentDescription = null
+                )
+            }
+        )
+        SplitToggleChip(
+            checked = true,
+            onCheckedChange = {},
+            label = { Text("SplitToggleChip") },
+            onClick = {},
+            toggleControl = {
+                Icon(
+                    imageVector =
+                    ToggleChipDefaults.radioIcon(checked = true),
+                    contentDescription = null
+                )
+            }
+        )
+    }
+}
+
+@Composable
+fun Picker(scrollState: ScrollState) {
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .verticalScroll(state = scrollState)
+            .padding(vertical = 16.dp)
+            .semantics { contentDescription = CONTENT_DESCRIPTION },
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        ListHeader { Text("Pickers") }
+        val items = listOf("One", "Two", "Three", "Four", "Five")
+        Picker(
+            state = rememberPickerState(items.size),
+            option = { Text(items[it]) },
+            modifier = Modifier.size(100.dp, 100.dp),
+        )
+    }
+}
+
+@Composable
+fun ProgressIndicators(navController: NavHostController) {
+    Column(
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        ListHeader { Text("Progress Indicators") }
+        // Test both circular progress indicator with gap and spinning indicator.
+        CompactChip(
+            onClick = { navController.navigate(PROGRESS_INDICATOR) },
+            colors = ChipDefaults.primaryChipColors(),
+            label = { Text(PROGRESS_INDICATOR) },
+            modifier = Modifier.semantics {
+                contentDescription = PROGRESS_INDICATOR
+            },
+        )
+        Spacer(Modifier.height(4.dp))
+        CompactChip(
+            onClick = {
+                navController.navigate(
+                    PROGRESS_INDICATOR_INDETERMINATE
+                )
+            },
+            colors = ChipDefaults.primaryChipColors(),
+            label = { Text(PROGRESS_INDICATOR_INDETERMINATE) },
+            modifier = Modifier.semantics {
+                contentDescription = PROGRESS_INDICATOR_INDETERMINATE
+            },
+        )
+    }
+}
+
+@Composable
+fun Slider() {
+    Column(
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        ListHeader { Text("Sliders") }
+        var value by remember { mutableStateOf(4.5f) }
+        InlineSlider(
+            value = value,
+            onValueChange = { value = it },
+            increaseIcon = {
+                Icon(
+                    InlineSliderDefaults.Increase,
+                    "Increase"
+                )
+            },
+            decreaseIcon = {
+                Icon(
+                    InlineSliderDefaults.Decrease,
+                    "Decrease"
+                )
+            },
+            valueRange = 3f..6f,
+            steps = 5,
+            segmented = false
+        )
+    }
+}
+
+@Composable
+fun Stepper() {
+    var value by remember { mutableStateOf(2f) }
+    Stepper(
+        value = value,
+        onValueChange = { value = it },
+        increaseIcon = { Icon(StepperDefaults.Increase, "Increase") },
+        decreaseIcon = { Icon(StepperDefaults.Decrease, "Decrease") },
+        valueRange = 1f..4f,
+        steps = 7
+    ) { Text("Value: $value") }
 }
 
 @Composable
@@ -248,73 +421,11 @@
 }
 
 @Composable
-fun Dialogs(navController: NavHostController) {
-    ListHeader { Text("Dialogs") }
-    CompactChip(
-        onClick = { navController.navigate(ALERT_DIALOG) },
-        colors = ChipDefaults.primaryChipColors(),
-        label = { Text(ALERT_DIALOG) },
-        modifier = Modifier.semantics { contentDescription = ALERT_DIALOG },
-    )
-    CompactChip(
-        onClick = { navController.navigate(CONFIRMATION_DIALOG) },
-        colors = ChipDefaults.primaryChipColors(),
-        label = { Text(CONFIRMATION_DIALOG) },
-        modifier = Modifier.semantics { contentDescription = CONFIRMATION_DIALOG },
-    )
-}
-
-@Composable
-fun Pickers() {
-    ListHeader { Text("Pickers") }
-    val items = listOf("One", "Two", "Three", "Four", "Five")
-    Picker(
-        state = rememberPickerState(items.size),
-        option = { Text(items[it]) },
-        modifier = Modifier.size(100.dp, 100.dp),
-    )
-}
-
-@Composable
-fun ProgressIndicators(navController: NavHostController) {
-    ListHeader { Text("Progress Indicators") }
-    // Test both circular progress indicator with gap and spinning indicator.
-    CompactChip(
-        onClick = { navController.navigate(PROGRESS_INDICATOR) },
-        colors = ChipDefaults.primaryChipColors(),
-        label = { Text(PROGRESS_INDICATOR) },
-        modifier = Modifier.semantics { contentDescription = PROGRESS_INDICATOR },
-    )
-    CompactChip(
-        onClick = { navController.navigate(PROGRESS_INDICATOR_INDETERMINATE) },
-        colors = ChipDefaults.primaryChipColors(),
-        label = { Text(PROGRESS_INDICATOR_INDETERMINATE) },
-        modifier = Modifier.semantics { contentDescription = PROGRESS_INDICATOR_INDETERMINATE },
-    )
-}
-
-@Composable
-fun Sliders() {
-    ListHeader { Text("Sliders") }
-    var value by remember { mutableStateOf(4.5f) }
-    InlineSlider(
-        value = value,
-        onValueChange = { value = it },
-        increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
-        decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
-        valueRange = 3f..6f,
-        steps = 5,
-        segmented = false
-    )
-}
-
-@Composable
-fun Steppers(navController: NavHostController) {
-    ListHeader { Text("Steppers") }
-    CompactChip(
-        onClick = { navController.navigate(STEPPER) },
-        colors = ChipDefaults.primaryChipColors(),
-        label = { Text(STEPPER) },
-        modifier = Modifier.semantics { contentDescription = STEPPER },
-    )
+fun Widget(navController: NavHostController, destination: String, text: String, desc: String) {
+    Button(
+        onClick = { navController.navigate(destination) },
+        modifier = Modifier.semantics { contentDescription = desc }
+    ) {
+        Text(text)
+    }
 }
diff --git a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt
index 74d435c..9114d23 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt
@@ -17,14 +17,15 @@
 package androidx.wear.compose.integration.macrobenchmark.test
 
 import android.content.Intent
-import android.graphics.Point
 import androidx.benchmark.macro.ExperimentalBaselineProfilesApi
 import androidx.benchmark.macro.junit4.BaselineProfileRule
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
 import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
 import androidx.testutils.createCompilationParams
 import org.junit.Before
 import org.junit.Rule
@@ -66,6 +67,13 @@
     private lateinit var device: UiDevice
     private val ALERT_DIALOG = "alert-dialog"
     private val CONFIRMATION_DIALOG = "confirmation-dialog"
+    private val BUTTONS = "buttons"
+    private val CARDS = "cards"
+    private val CHIPS = "chips"
+    private val DIALOGS = "dialogs"
+    private val PICKER = "picker"
+    private val PROGRESSINDICATORS = "progressindicators"
+    private val SLIDER = "slider"
     private val STEPPER = "stepper"
     private val PROGRESS_INDICATOR = "progress-indicator"
     private val PROGRESS_INDICATOR_INDETERMINATE = "progress-indicator-indeterminate"
@@ -84,37 +92,46 @@
                 val intent = Intent()
                 intent.action = ACTION
                 startActivityAndWait(intent)
-                testDestination(ALERT_DIALOG)
-                scrollDown()
-                testDestination(CONFIRMATION_DIALOG)
-                scrollDown()
-                testDestination(STEPPER)
-                scrollDown()
-                testDestination(PROGRESS_INDICATOR)
-                scrollDown()
-                testDestination(PROGRESS_INDICATOR_INDETERMINATE)
-
-                repeat(30) {
-                    scrollDown()
-                }
+                testDestination(description = BUTTONS)
+                testDestination(description = CARDS)
+                testDestination(description = CHIPS)
+                testDialogs()
+                testDestination(description = PICKER)
+                testProgressIndicators()
+                testDestination(description = SLIDER)
+                testDestination(description = STEPPER)
             }
         )
     }
 
-    private fun testDestination(name: String) {
-        device.findObject(By.desc(name)).click()
+    private fun testDialogs() {
+        findAndClick(By.desc(DIALOGS))
+        device.waitForIdle()
+        testDestination(description = ALERT_DIALOG)
+        testDestination(description = CONFIRMATION_DIALOG)
+        device.pressBack()
+        device.waitForIdle()
+    }
+
+    private fun testProgressIndicators() {
+        findAndClick(By.desc(PROGRESSINDICATORS))
+        device.waitForIdle()
+        testDestination(description = PROGRESS_INDICATOR)
+        testDestination(description = PROGRESS_INDICATOR_INDETERMINATE)
+        device.pressBack()
+        device.waitForIdle()
+    }
+
+    private fun testDestination(description: String) {
+        findAndClick(By.desc(description))
         device.waitForIdle()
         device.pressBack()
         device.waitForIdle()
     }
 
-    private fun scrollDown() {
-        // Scroll down to view remaining UI elements
-        // Setting a gesture margin is important otherwise gesture nav is triggered.
-        val list = device.findObject(By.desc(CONTENT_DESCRIPTION))
-        list.setGestureMargin(device.displayWidth / 5)
-        list.drag(Point(list.visibleCenter.x, list.visibleCenter.y / 3))
-        device.waitForIdle()
+    private fun findAndClick(selector: BySelector) {
+        device.wait(Until.findObject(selector), 3000)
+        device.findObject(selector).click()
     }
 
     companion object {
diff --git a/wear/tiles/OWNERS b/wear/tiles/OWNERS
index 7c0968e..b831820 100644
--- a/wear/tiles/OWNERS
+++ b/wear/tiles/OWNERS
@@ -1,3 +1,4 @@
 # Bug component: 1112272
 jgarside@google.com
-jnichol@google.com
\ No newline at end of file
+jnichol@google.com
+msab@google.com
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java
index 37fe4c6..dc199bd 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java
@@ -44,6 +44,8 @@
  * An {@link Activity} to exercise {@link WebViewCompat#addDocumentStartJavaScript(WebView, String,
  * Set)} related functionality.
  */
+// TODO(swestphal): Remove the @SuppressLint after addDocumentStartJavaScript is unhidden.
+@SuppressLint("RestrictedApi")
 public class DocumentStartJavaScriptActivity extends AppCompatActivity {
     private final Uri mExampleUri = new Uri.Builder()
                                             .scheme("https")
diff --git a/webkit/webkit/api/current.txt b/webkit/webkit/api/current.txt
index fa38e7f..c0537d9 100644
--- a/webkit/webkit/api/current.txt
+++ b/webkit/webkit/api/current.txt
@@ -45,10 +45,6 @@
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
   }
 
-  public abstract class ScriptHandler {
-    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void remove();
-  }
-
   public abstract class ServiceWorkerClientCompat {
     ctor public ServiceWorkerClientCompat();
     method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
@@ -193,7 +189,6 @@
   }
 
   public class WebViewCompat {
-    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ScriptHandler addDocumentStartJavaScript(android.webkit.WebView, String, java.util.Set<java.lang.String!>);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
     method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
@@ -227,7 +222,6 @@
     field public static final String ALGORITHMIC_DARKENING = "ALGORITHMIC_DARKENING";
     field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
     field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
-    field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
     field public static final String FORCE_DARK = "FORCE_DARK";
     field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
     field public static final String GET_VARIATIONS_HEADER = "GET_VARIATIONS_HEADER";
diff --git a/webkit/webkit/api/public_plus_experimental_current.txt b/webkit/webkit/api/public_plus_experimental_current.txt
index fa38e7f..c0537d9 100644
--- a/webkit/webkit/api/public_plus_experimental_current.txt
+++ b/webkit/webkit/api/public_plus_experimental_current.txt
@@ -45,10 +45,6 @@
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
   }
 
-  public abstract class ScriptHandler {
-    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void remove();
-  }
-
   public abstract class ServiceWorkerClientCompat {
     ctor public ServiceWorkerClientCompat();
     method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
@@ -193,7 +189,6 @@
   }
 
   public class WebViewCompat {
-    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ScriptHandler addDocumentStartJavaScript(android.webkit.WebView, String, java.util.Set<java.lang.String!>);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
     method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
@@ -227,7 +222,6 @@
     field public static final String ALGORITHMIC_DARKENING = "ALGORITHMIC_DARKENING";
     field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
     field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
-    field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
     field public static final String FORCE_DARK = "FORCE_DARK";
     field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
     field public static final String GET_VARIATIONS_HEADER = "GET_VARIATIONS_HEADER";
diff --git a/webkit/webkit/api/restricted_current.txt b/webkit/webkit/api/restricted_current.txt
index fa38e7f..c0537d9 100644
--- a/webkit/webkit/api/restricted_current.txt
+++ b/webkit/webkit/api/restricted_current.txt
@@ -45,10 +45,6 @@
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
   }
 
-  public abstract class ScriptHandler {
-    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void remove();
-  }
-
   public abstract class ServiceWorkerClientCompat {
     ctor public ServiceWorkerClientCompat();
     method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
@@ -193,7 +189,6 @@
   }
 
   public class WebViewCompat {
-    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ScriptHandler addDocumentStartJavaScript(android.webkit.WebView, String, java.util.Set<java.lang.String!>);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
     method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
@@ -227,7 +222,6 @@
     field public static final String ALGORITHMIC_DARKENING = "ALGORITHMIC_DARKENING";
     field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
     field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
-    field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
     field public static final String FORCE_DARK = "FORCE_DARK";
     field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
     field public static final String GET_VARIATIONS_HEADER = "GET_VARIATIONS_HEADER";
diff --git a/webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java b/webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java
index db7447e..2aac172 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java
@@ -25,7 +25,11 @@
  * corresponding JavaScript script should be removed.
  *
  * @see WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView, String, Set)
+ *
+ * TODO(swestphal): unhide when ready.
+ * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public abstract class ScriptHandler {
     /**
      * Removes the corresponding script, it will take effect from next page load.
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index 814b9d7..8073dfa 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -31,6 +31,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresFeature;
+import androidx.annotation.RestrictTo;
 import androidx.annotation.UiThread;
 import androidx.webkit.internal.WebMessagePortImpl;
 import androidx.webkit.internal.WebViewFeatureInternal;
@@ -724,7 +725,11 @@
      * @throws IllegalArgumentException If one of the {@code allowedOriginRules} is invalid.
      * @see #addWebMessageListener(WebView, String, Set, WebMessageListener)
      * @see ScriptHandler
+     *
+     * TODO(swestphal): unhide when ready.
+     * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @RequiresFeature(
             name = WebViewFeature.DOCUMENT_START_SCRIPT,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index 1eef16b..5de5148 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -460,7 +460,11 @@
      * Feature for {@link #isFeatureSupported(String)}.
      * This feature covers {@link WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,
      * String, Set)}.
+     *
+     * TODO(swestphal): unhide when ready.
+     * @hide
      */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
 
     /**