Merge "Read property accessor meteadata in KAPT" into androidx-main
diff --git a/OWNERS b/OWNERS
index 8089367..5d06014 100644
--- a/OWNERS
+++ b/OWNERS
@@ -13,7 +13,6 @@
 mount@google.com
 nickanthony@google.com
 owengray@google.com
-pavlis@google.com
 romainguy@android.com
 saff@google.com
 sergeyv@google.com
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/CompilationMode.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/CompilationMode.kt
index 6a11376..19cc93a 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/CompilationMode.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/CompilationMode.kt
@@ -185,12 +185,9 @@
                 )
             }
         }
-        // Kill Process
+        // Kill process before compiling
         Log.d(TAG, "Killing process $packageName")
         Shell.executeCommand("am force-stop $packageName")
-        // Compile
-        compilePackage(packageName)
-        Log.d(TAG, "$packageName is compiled.")
     } else if (this is SpeedProfile) {
         repeat(this.warmupIterations) {
             block()
@@ -204,8 +201,8 @@
             Log.d(TAG, "Received dump profile response $response")
             throw RuntimeException("Failed to dump profile for $packageName ($response)")
         }
-        compilePackage(packageName)
     }
+    compilePackage(packageName)
 }
 
 /**
diff --git a/benchmark/integration-tests/macrobenchmark-target/build.gradle b/benchmark/integration-tests/macrobenchmark-target/build.gradle
index 2ef7963..20dd547 100644
--- a/benchmark/integration-tests/macrobenchmark-target/build.gradle
+++ b/benchmark/integration-tests/macrobenchmark-target/build.gradle
@@ -35,6 +35,7 @@
     implementation(libs.constraintLayout)
     implementation(project(":appcompat:appcompat"))
     implementation(project(":activity:activity"))
+    implementation(project(":profileinstaller:profileinstaller"))
     implementation(project(":recyclerview:recyclerview"))
     implementation(project(":tracing:tracing-ktx"))
     implementation(libs.material)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index a06cdbd..737f8f0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -178,16 +178,13 @@
                 }
             }
 
-            // TODO(148540713): remove this exclusion when Lint can support using multiple lint jars
-            configurations.getByName("lintChecks").exclude(
-                mapOf("module" to "lint-checks")
-            )
             // TODO: figure out how to apply this to multiplatform modules
             dependencies.add(
                 "lintChecks",
                 project.dependencies.project(
                     mapOf(
                         "path" to ":compose:lint:internal-lint-checks",
+                        // TODO(b/206617878) remove this shadow configuration
                         "configuration" to "shadow"
                     )
                 )
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index cb6f0f2..47ac289 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -169,10 +169,13 @@
             Sync::class.java
         ) { task ->
             val sources = docsConfiguration.incoming.artifactView { }.files
+            // Store archiveOperations into a local variable to prevent access to the plugin
+            // during the task execution, as that breaks configuration caching.
+            val localVar = archiveOperations
             task.from(
                 sources.elements.map { jars ->
                     jars.map {
-                        archiveOperations.zipTree(it).matching {
+                        localVar.zipTree(it).matching {
                             // Filter out files that documentation tools cannot process.
                             it.exclude("**/*.MF")
                             it.exclude("**/*.aidl")
@@ -206,13 +209,15 @@
         docsConfiguration: Configuration
     ): TaskProvider<Sync> {
         return project.tasks.register("unzipSourcesForDackka", Sync::class.java) { task ->
-
             val sources = docsConfiguration.incoming.artifactView { }.files
 
+            // Store archiveOperations into a local variable to prevent access to the plugin
+            // during the task execution, as that breaks configuration caching.
+            val localVar = archiveOperations
             task.from(
                 sources.elements.map { jars ->
                     jars.map {
-                        archiveOperations.zipTree(it)
+                        localVar.zipTree(it)
                     }
                 }
             )
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
index 9522e20..d31a45c 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -23,7 +23,7 @@
     val ACTIVITY = Version("1.5.0-alpha01")
     val ADS_IDENTIFIER = Version("1.0.0-alpha05")
     val ANNOTATION = Version("1.4.0-alpha01")
-    val ANNOTATION_EXPERIMENTAL = Version("1.2.0-beta02")
+    val ANNOTATION_EXPERIMENTAL = Version("1.2.0-rc01")
     val APPCOMPAT = Version("1.5.0-alpha01")
     val APPSEARCH = Version("1.0.0-alpha05")
     val ARCH_CORE = Version("2.2.0-alpha01")
@@ -104,7 +104,7 @@
     val RECYCLERVIEW_SELECTION = Version("1.2.0-alpha02")
     val REMOTECALLBACK = Version("1.0.0-alpha02")
     val RESOURCEINSPECTION = Version("1.1.0-alpha01")
-    val ROOM = Version("2.4.0-beta03")
+    val ROOM = Version("2.4.0-rc01")
     val SAVEDSTATE = Version("1.2.0-alpha01")
     val SECURITY = Version("1.1.0-alpha04")
     val SECURITY_APP_AUTHENTICATOR = Version("1.0.0-alpha03")
@@ -118,7 +118,7 @@
     val SLICE_REMOTECALLBACK = Version("1.0.0-alpha01")
     val SLIDINGPANELAYOUT = Version("1.2.0-rc01")
     val STARTUP = Version("1.2.0-alpha01")
-    val SQLITE = Version("2.2.0-beta03")
+    val SQLITE = Version("2.2.0-rc01")
     val SQLITE_INSPECTOR = Version("2.1.0-alpha01")
     val SWIPEREFRESHLAYOUT = Version("1.2.0-alpha01")
     val TESTSCREENSHOT = Version("1.0.0-alpha01")
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt b/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
index 931fa0a..48197b5 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
@@ -27,13 +27,7 @@
     const val DEFAULT_MIN_SDK_VERSION = 14
     const val INSTRUMENTATION_RUNNER = "androidx.test.runner.AndroidJUnitRunner"
     const val BUILD_TOOLS_VERSION = "30.0.3"
-    val NDK_VERSION by lazy {
-        // TODO(aurimas) b/173737578 remove when we no longer have divergent versions
-        when (getOperatingSystem()) {
-            OperatingSystem.LINUX -> "23.0.7243079"
-            else -> "23.0.7599858"
-        }
-    }
+    const val NDK_VERSION = "23.1.7779620"
 
     /**
      * The Android SDK version to use for compilation.
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 be5dfad..a08ceb5 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
@@ -53,6 +53,7 @@
 import androidx.camera.core.impl.SurfaceSizeDefinition;
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.utils.CameraOrientationUtil;
+import androidx.camera.core.impl.utils.CompareSizesByArea;
 import androidx.core.util.Preconditions;
 
 import java.util.ArrayList;
@@ -1334,34 +1335,6 @@
         return excludedSizes;
     }
 
-    /** Comparator based on area of the given {@link Size} objects. */
-    @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-    static final class CompareSizesByArea implements Comparator<Size> {
-        private boolean mReverse = false;
-
-        CompareSizesByArea() {
-        }
-
-        CompareSizesByArea(boolean reverse) {
-            mReverse = reverse;
-        }
-
-        @Override
-        public int compare(Size lhs, Size rhs) {
-            // We cast here to ensure the multiplications won't overflow
-            int result =
-                    Long.signum(
-                            (long) lhs.getWidth() * lhs.getHeight()
-                                    - (long) rhs.getWidth() * rhs.getHeight());
-
-            if (mReverse) {
-                result *= -1;
-            }
-
-            return result;
-        }
-    }
-
     /** Comparator based on how close they are to the target aspect ratio. */
     @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
     static final class CompareAspectRatiosByDistanceToTargetRatio implements Comparator<Rational> {
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.java
index 3561b59..c3117bc 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.java
@@ -61,6 +61,7 @@
 import androidx.camera.core.impl.SurfaceConfig.ConfigType;
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.UseCaseConfigFactory;
+import androidx.camera.core.impl.utils.CompareSizesByArea;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.internal.CameraUseCaseAdapter;
 import androidx.camera.testing.CameraUtil;
@@ -1204,7 +1205,7 @@
         }
 
         // The testing sizes array will be equal to mSupportedSizes after sorting.
-        Arrays.sort(sizes, new SupportedSurfaceCombination.CompareSizesByArea(true));
+        Arrays.sort(sizes, new CompareSizesByArea(true));
         assertThat(Arrays.asList(sizes)).isEqualTo(Arrays.asList(mSupportedSizes));
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/CompareSizesByArea.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/CompareSizesByArea.java
new file mode 100644
index 0000000..a5ce6a8
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/CompareSizesByArea.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.impl.utils;
+
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.util.Comparator;
+
+/** Comparator based on area of the given {@link Size} objects. */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public final class CompareSizesByArea implements Comparator<Size> {
+    private boolean mReverse = false;
+
+    /** Creates a comparator with standard total ordering. */
+    public CompareSizesByArea() {
+        this(false);
+    }
+
+    /** Creates a comparator which can reverse the total ordering. */
+    public CompareSizesByArea(boolean reverse) {
+        mReverse = reverse;
+    }
+
+    @Override
+    public int compare(@NonNull Size lhs, @NonNull Size rhs) {
+        // We cast here to ensure the multiplications won't overflow
+        int result =
+                Long.signum(
+                        (long) lhs.getWidth() * lhs.getHeight()
+                                - (long) rhs.getWidth() * rhs.getHeight());
+
+        if (mReverse) {
+            result *= -1;
+        }
+
+        return result;
+    }
+}
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
index cdb46b4..a870135 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/internal/AudioSourceTest.kt
@@ -68,14 +68,16 @@
         }
         fakeBufferProvider.setActive(true)
 
-        audioSource = AudioSource.Builder()
-            .setExecutor(CameraXExecutors.ioExecutor())
-            .setAudioSource(AUDIO_SOURCE)
-            .setSampleRate(SAMPLE_RATE)
-            .setChannelCount(CHANNEL_COUNT)
-            .setAudioFormat(AUDIO_FORMAT)
-            .setBufferProvider(fakeBufferProvider)
-            .build()
+        audioSource = AudioSource(
+            AudioSource.Settings.builder()
+                .setAudioSource(AUDIO_SOURCE)
+                .setSampleRate(SAMPLE_RATE)
+                .setChannelCount(CHANNEL_COUNT)
+                .setAudioFormat(AUDIO_FORMAT)
+                .build(),
+            CameraXExecutors.ioExecutor()
+        )
+        audioSource.setBufferProvider(fakeBufferProvider)
     }
 
     @After
@@ -123,4 +125,44 @@
             verify(bufferFactoryInvocations, noInvocation(3000L, 6000L)).call()
         }
     }
+
+    @Test
+    fun canResetBufferProvider_beforeStarting() {
+        // Arrange
+        val localBufferFactoryInvocations = mock(Callable::class.java)
+        val localFakeBufferProvider = FakeBufferProvider {
+            localBufferFactoryInvocations.call()
+            FakeInputBuffer()
+        }
+
+        // Act
+        audioSource.setBufferProvider(localFakeBufferProvider)
+        audioSource.start()
+        localFakeBufferProvider.setActive(true)
+
+        // Assert.
+        // It should continuously send audio data by invoking BufferProvider#acquireBuffer
+        verify(localBufferFactoryInvocations, timeout(10000L).atLeast(3)).call()
+    }
+
+    @Test
+    fun canResetBufferProvider_afterStarting() {
+        // Arrange
+        val localBufferFactoryInvocations = mock(Callable::class.java)
+        val localFakeBufferProvider = FakeBufferProvider {
+            localBufferFactoryInvocations.call()
+            FakeInputBuffer()
+        }
+        audioSource.start()
+        fakeBufferProvider.setActive(true)
+        verify(bufferFactoryInvocations, timeout(10000L).atLeast(3)).call()
+
+        // Act
+        audioSource.setBufferProvider(localFakeBufferProvider)
+        localFakeBufferProvider.setActive(true)
+
+        // Assert.
+        // It should continuously send audio data by invoking BufferProvider#acquireBuffer
+        verify(localBufferFactoryInvocations, timeout(10000L).atLeast(3)).call()
+    }
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
index 99a635a9..c4acf18 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
@@ -63,7 +63,6 @@
 import androidx.camera.core.impl.utils.futures.Futures;
 import androidx.camera.video.internal.AudioSource;
 import androidx.camera.video.internal.AudioSourceAccessException;
-import androidx.camera.video.internal.BufferProvider;
 import androidx.camera.video.internal.ResourceCreationException;
 import androidx.camera.video.internal.compat.Api26Impl;
 import androidx.camera.video.internal.compat.quirk.DeactivateEncoderSurfaceBeforeStopEncoderQuirk;
@@ -74,7 +73,6 @@
 import androidx.camera.video.internal.encoder.Encoder;
 import androidx.camera.video.internal.encoder.EncoderCallback;
 import androidx.camera.video.internal.encoder.EncoderImpl;
-import androidx.camera.video.internal.encoder.InputBuffer;
 import androidx.camera.video.internal.encoder.InvalidConfigException;
 import androidx.camera.video.internal.encoder.OutputConfig;
 import androidx.camera.video.internal.encoder.VideoEncoderConfig;
@@ -1103,38 +1101,44 @@
     @ExecutedBy("mSequentialExecutor")
     private void setupAudio() throws ResourceCreationException {
         MediaSpec mediaSpec = getObservableData(mMediaSpec);
-        AudioEncoderConfig config = composeAudioEncoderConfig(mediaSpec);
+        // Create the audio source
+        AudioSpec audioSpec = mediaSpec.getAudioSpec();
+        AudioSource.Settings audioSourceSettings = AudioSource.Settings.builder()
+                .setAudioSource(audioSpec.getSource())
+                .setSampleRate(selectSampleRate(audioSpec))
+                .setChannelCount(audioSpec.getChannelCount())
+                .setAudioFormat(audioSpec.getSourceFormat())
+                .build();
 
         try {
+            mAudioSource = setupAudioSource(audioSourceSettings);
+        } catch (AudioSourceAccessException e) {
+            throw new ResourceCreationException(e);
+        }
+
+
+        // Create the audio encoder
+        AudioEncoderConfig config = composeAudioEncoderConfig(mediaSpec);
+        try {
             mAudioEncoder = new EncoderImpl(mExecutor, config);
         } catch (InvalidConfigException e) {
             throw new ResourceCreationException(e);
         }
 
+        // Connect the audio source to the audio encoder
         Encoder.EncoderInput bufferProvider = mAudioEncoder.getInput();
         if (!(bufferProvider instanceof Encoder.ByteBufferInput)) {
             throw new AssertionError("The EncoderInput of audio isn't a ByteBufferInput.");
         }
-        try {
-            mAudioSource = setupAudioSource((Encoder.ByteBufferInput) bufferProvider,
-                    mediaSpec.getAudioSpec());
-        } catch (AudioSourceAccessException e) {
-            throw new ResourceCreationException(e);
-        }
+        mAudioSource.setBufferProvider((Encoder.ByteBufferInput) bufferProvider);
     }
 
     @RequiresPermission(Manifest.permission.RECORD_AUDIO)
     @NonNull
-    private AudioSource setupAudioSource(@NonNull BufferProvider<InputBuffer> bufferProvider,
-            @NonNull AudioSpec audioSpec) throws AudioSourceAccessException {
-        AudioSource audioSource = new AudioSource.Builder()
-                .setExecutor(CameraXExecutors.ioExecutor())
-                .setBufferProvider(bufferProvider)
-                .setAudioSource(audioSpec.getSource())
-                .setSampleRate(selectSampleRate(audioSpec))
-                .setChannelCount(audioSpec.getChannelCount())
-                .setAudioFormat(audioSpec.getSourceFormat())
-                .build();
+    private AudioSource setupAudioSource(@NonNull AudioSource.Settings audioSourceSettings)
+            throws AudioSourceAccessException {
+        AudioSource audioSource = new AudioSource(audioSourceSettings,
+                CameraXExecutors.ioExecutor());
         audioSource.setAudioSourceCallback(mSequentialExecutor,
                 new AudioSource.AudioSourceCallback() {
                     @Override
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/AudioSource.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/AudioSource.java
index 6a6dd23..aad2189 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/AudioSource.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/AudioSource.java
@@ -21,6 +21,7 @@
 import static androidx.camera.video.internal.AudioSource.InternalState.STARTED;
 
 import android.Manifest;
+import android.annotation.SuppressLint;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioRecord;
@@ -28,6 +29,7 @@
 import android.media.AudioTimestamp;
 import android.os.Build;
 
+import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -43,6 +45,8 @@
 import androidx.camera.video.internal.encoder.InputBuffer;
 import androidx.core.util.Preconditions;
 
+import com.google.auto.value.AutoValue;
+
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Collections;
@@ -55,7 +59,7 @@
  * AudioSource is used to obtain audio raw data and write to the buffer from {@link BufferProvider}.
  *
  * <p>The audio raw data could be one of sources from the device. The target source can be
- * specified with {@link Builder#setAudioSource(int)}.
+ * specified with {@link Settings.Builder#setAudioSource(int)}.
  *
  * <p>Calling {@link #start} will start reading audio data from the target source and then write
  * the data into the buffer from {@link BufferProvider}. Calling {@link #stop} will stop sending
@@ -87,8 +91,6 @@
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     final Executor mExecutor;
 
-    private final BufferProvider<InputBuffer> mBufferProvider;
-
     private AudioManager.AudioRecordingCallback mAudioRecordingCallback;
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
@@ -115,28 +117,50 @@
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     AudioSourceCallback mAudioSourceCallback;
 
+    // The following should only be accessed by mExecutor
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+    BufferProvider<InputBuffer> mBufferProvider;
+    private FutureCallback<InputBuffer> mAcquireBufferCallback;
+    private Observable.Observer<BufferProvider.State> mStateObserver;
+
+
+    /**
+     * Creates an AudioSource for the given settings.
+     *
+     * <p>It should be verified the combination of sample rate, channel count and audio format is
+     * supported with {@link #isSettingsSupported(int, int, int)} before passing the settings to
+     * this constructor, or an {@link UnsupportedOperationException} will be thrown.
+     *
+     * @throws UnsupportedOperationException if the combination of sample rate, channel count,
+     * and audio format in the provided settings is unsupported.
+     * @throws AudioSourceAccessException if the audio device is not available or cannot be
+     * initialized with the given settings.
+     */
     @RequiresPermission(Manifest.permission.RECORD_AUDIO)
-    AudioSource(@NonNull Executor executor,
-            @NonNull BufferProvider<InputBuffer> bufferProvider,
-            int audioSource,
-            int sampleRate,
-            int channelCount,
-            int audioFormat)
+    public AudioSource(@NonNull Settings settings, @NonNull Executor executor)
             throws AudioSourceAccessException {
-        int minBufferSize = getMinBufferSize(sampleRate, channelCount, audioFormat);
+        if (!isSettingsSupported(settings.getSampleRate(), settings.getChannelCount(),
+                settings.getAudioFormat())) {
+            throw new UnsupportedOperationException(String.format(
+                    "The combination of sample rate %d, channel count %d and audio format"
+                            + " %d is not supported.",
+                    settings.getSampleRate(), settings.getChannelCount(),
+                    settings.getAudioFormat()));
+        }
+
+        int minBufferSize = getMinBufferSize(settings.getSampleRate(), settings.getChannelCount(),
+                settings.getAudioFormat());
         // The minBufferSize should be a positive value since the settings had already been checked
         // by the isSettingsSupported().
         Preconditions.checkState(minBufferSize > 0);
 
         mExecutor = CameraXExecutors.newSequentialExecutor(executor);
-        mBufferProvider = bufferProvider;
         mBufferSize = minBufferSize * 2;
         try {
-            mAudioRecord = new AudioRecord(audioSource,
-                    sampleRate,
-                    channelCountToChannelConfig(channelCount),
-                    audioFormat,
+            mAudioRecord = new AudioRecord(settings.getAudioSource(),
+                    settings.getSampleRate(),
+                    channelCountToChannelConfig(settings.getChannelCount()),
+                    settings.getAudioFormat(),
                     mBufferSize);
         } catch (IllegalArgumentException e) {
             throw new AudioSourceAccessException("Unable to create AudioRecord", e);
@@ -152,8 +176,6 @@
             Api29Impl.registerAudioRecordingCallback(mAudioRecord, mExecutor,
                     mAudioRecordingCallback);
         }
-
-        mBufferProvider.addObserver(mExecutor, mStateObserver);
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
@@ -178,9 +200,38 @@
         }
     }
 
+
+    /**
+     * Sets the {@link BufferProvider}.
+     *
+     * <p>A buffer provider is required to stream audio. If no buffer provider is provided, then
+     * audio will be dropped until one is provided and active.
+     *
+     * @param bufferProvider The new buffer provider to use.
+     */
+    public void setBufferProvider(@NonNull BufferProvider<InputBuffer> bufferProvider) {
+        mExecutor.execute(() -> {
+            switch (mState) {
+                case CONFIGURED:
+                    // Fall-through
+                case STARTED:
+                    if (mBufferProvider != bufferProvider) {
+                        resetBufferProvider(bufferProvider);
+                    }
+                    break;
+                case RELEASED:
+                    throw new IllegalStateException("AudioRecorder is released");
+            }
+        });
+    }
+
     /**
      * Starts the AudioSource.
      *
+     * <p>Before starting, a {@link BufferProvider} should be set with
+     * {@link #setBufferProvider(BufferProvider)}. If a buffer provider is not set, audio data
+     * will be dropped.
+     *
      * <p>Audio data will start being sent to the {@link BufferProvider} when
      * {@link BufferProvider}'s state is {@link BufferProvider.State#ACTIVE}.
      *
@@ -236,7 +287,7 @@
                 case STARTED:
                     // Fall-through
                 case CONFIGURED:
-                    mBufferProvider.removeObserver(mStateObserver);
+                    resetBufferProvider(null);
                     if (Build.VERSION.SDK_INT >= 29) {
                         Api29Impl.unregisterAudioRecordingCallback(mAudioRecord,
                                 mAudioRecordingCallback);
@@ -277,6 +328,75 @@
         });
     }
 
+    @ExecutedBy("mExecutor")
+    private void resetBufferProvider(@Nullable BufferProvider<InputBuffer> bufferProvider) {
+        if (mBufferProvider != null) {
+            mBufferProvider.removeObserver(mStateObserver);
+            mBufferProvider = null;
+            mStateObserver = null;
+            mAcquireBufferCallback = null;
+        }
+        mBufferProviderState = BufferProvider.State.INACTIVE;
+        updateSendingAudio();
+        if (bufferProvider != null) {
+            mBufferProvider = bufferProvider;
+            mStateObserver = new Observable.Observer<BufferProvider.State>() {
+                @ExecutedBy("mExecutor")
+                @Override
+                public void onNewData(@Nullable BufferProvider.State state) {
+                    if (mBufferProvider == bufferProvider) {
+                        Logger.d(TAG, "Receive BufferProvider state change: "
+                                + mBufferProviderState + " to " + state);
+                        mBufferProviderState = state;
+                        updateSendingAudio();
+                    }
+                }
+
+                @ExecutedBy("mExecutor")
+                @Override
+                public void onError(@NonNull Throwable throwable) {
+                    if (mBufferProvider == bufferProvider) {
+                        notifyError(throwable);
+                    }
+                }
+            };
+
+            mAcquireBufferCallback = new FutureCallback<InputBuffer>() {
+                @ExecutedBy("mExecutor")
+                @Override
+                public void onSuccess(InputBuffer inputBuffer) {
+                    if (!mIsSendingAudio || mBufferProvider != bufferProvider) {
+                        inputBuffer.cancel();
+                        return;
+                    }
+                    ByteBuffer byteBuffer = inputBuffer.getByteBuffer();
+
+                    int length = mAudioRecord.read(byteBuffer, mBufferSize);
+                    if (length > 0) {
+                        byteBuffer.limit(length);
+                        inputBuffer.setPresentationTimeUs(generatePresentationTimeUs());
+                        inputBuffer.submit();
+                    } else {
+                        Logger.w(TAG, "Unable to read data from AudioRecord.");
+                        inputBuffer.cancel();
+                    }
+                    sendNextAudio();
+                }
+
+                @ExecutedBy("mExecutor")
+                @Override
+                public void onFailure(Throwable throwable) {
+                    if (mBufferProvider != bufferProvider) {
+                        Logger.d(TAG, "Unable to get input buffer, the BufferProvider "
+                                + "could be transitioning to INACTIVE state.");
+                        notifyError(throwable);
+                    }
+                }
+            };
+            mBufferProvider.addObserver(mExecutor, mStateObserver);
+        }
+    }
+
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     void notifyError(Throwable throwable) {
         if (mCallbackExecutor != null && mAudioSourceCallback != null) {
@@ -368,56 +488,6 @@
         return presentationTimeUs;
     }
 
-    private final FutureCallback<InputBuffer> mAcquireBufferCallback =
-            new FutureCallback<InputBuffer>() {
-                @ExecutedBy("mExecutor")
-                @Override
-                public void onSuccess(InputBuffer inputBuffer) {
-                    if (!mIsSendingAudio) {
-                        inputBuffer.cancel();
-                        return;
-                    }
-                    ByteBuffer byteBuffer = inputBuffer.getByteBuffer();
-
-                    int length = mAudioRecord.read(byteBuffer, mBufferSize);
-                    if (length > 0) {
-                        byteBuffer.limit(length);
-                        inputBuffer.setPresentationTimeUs(generatePresentationTimeUs());
-                        inputBuffer.submit();
-                    } else {
-                        Logger.w(TAG, "Unable to read data from AudioRecord.");
-                        inputBuffer.cancel();
-                    }
-                    sendNextAudio();
-                }
-
-                @ExecutedBy("mExecutor")
-                @Override
-                public void onFailure(Throwable throwable) {
-                    Logger.d(TAG, "Unable to get input buffer, the BufferProvider "
-                            + "could be transitioning to INACTIVE state.");
-                    notifyError(throwable);
-                }
-            };
-
-    private final Observable.Observer<BufferProvider.State> mStateObserver =
-            new Observable.Observer<BufferProvider.State>() {
-                @ExecutedBy("mExecutor")
-                @Override
-                public void onNewData(@Nullable BufferProvider.State state) {
-                    Logger.d(TAG, "Receive BufferProvider state change: "
-                            + mBufferProviderState + " to " + state);
-                    mBufferProviderState = state;
-                    updateSendingAudio();
-                }
-
-                @ExecutedBy("mExecutor")
-                @Override
-                public void onError(@NonNull Throwable throwable) {
-                    notifyError(throwable);
-                }
-            };
-
     /** Check if the combination of sample rate, channel count and audio format is supported. */
     public static boolean isSettingsSupported(int sampleRate, int channelCount, int audioFormat) {
         if (sampleRate <= 0 || channelCount <= 0) {
@@ -436,124 +506,129 @@
     }
 
     /**
-     * The builder of the AudioSource.
+     * Settings required to configure the audio source.
      */
-    public static class Builder {
-        private Executor mExecutor;
-        private int mAudioSource = -1;
-        private int mSampleRate = -1;
-        private int mChannelCount = -1;
-        private int mAudioFormat = -1;
-        private BufferProvider<InputBuffer> mBufferProvider;
+    @AutoValue
+    public abstract static class Settings {
 
-        /** Sets the executor to run the background task. */
+        /** Creates a builder for these settings. */
+        @SuppressLint("Range") // Need to initialize as invalid values
         @NonNull
-        public Builder setExecutor(@NonNull Executor executor) {
-            mExecutor = Preconditions.checkNotNull(executor);
-            return this;
+        public static Settings.Builder builder() {
+            return new AutoValue_AudioSource_Settings.Builder()
+                    .setAudioSource(-1)
+                    .setSampleRate(-1)
+                    .setChannelCount(-1)
+                    .setAudioFormat(-1);
         }
 
         /**
-         * Sets the device audio source.
+         * Gets the device audio source.
          *
          * @see android.media.MediaRecorder.AudioSource#MIC
          * @see android.media.MediaRecorder.AudioSource#CAMCORDER
          */
-        @NonNull
-        public Builder setAudioSource(int audioSource) {
-            mAudioSource = audioSource;
-            return this;
-        }
+        public abstract int getAudioSource();
 
         /**
-         * Sets the audio sample rate.
-         *
-         * <p>It has to ensure the combination of sample rate, channel count and audio format is
-         * supported by {@link AudioSource#isSettingsSupported(int, int, int)}.
-         *
-         * @throws IllegalArgumentException if the sample rate is not positive.
+         * Gets the audio sample rate.
          */
-        @NonNull
-        public Builder setSampleRate(int sampleRate) {
-            Preconditions.checkArgument(sampleRate > 0);
-            mSampleRate = sampleRate;
-            return this;
-        }
+        @IntRange(from = 1)
+        public abstract int getSampleRate();
 
         /**
-         * Sets the channel count.
-         *
-         * <p>It has to ensure the combination of sample rate, channel count and audio format is
-         * supported by {@link AudioSource#isSettingsSupported(int, int, int)}.
-         *
-         * @throws IllegalArgumentException if the channel count is not positive.
+         * Gets the channel count.
          */
-        @NonNull
-        public Builder setChannelCount(int channelCount) {
-            Preconditions.checkArgument(channelCount > 0);
-            mChannelCount = channelCount;
-            return this;
-        }
+        @IntRange(from = 1)
+        public abstract int getChannelCount();
 
         /**
          * Sets the audio format.
          *
-         * <p>It has to ensure the combination of sample rate, channel count and audio format is
-         * supported by {@link AudioSource#isSettingsSupported(int, int, int)}.
-         *
          * @see AudioFormat#ENCODING_PCM_16BIT
          */
-        @NonNull
-        public Builder setAudioFormat(int audioFormat) {
-            mAudioFormat = audioFormat;
-            return this;
+        public abstract int getAudioFormat();
+
+        // Should not be instantiated directly
+        Settings() {
         }
 
-        /** Sets the {@link BufferProvider}. */
-        @NonNull
-        public Builder setBufferProvider(@NonNull BufferProvider<InputBuffer> bufferProvider) {
-            mBufferProvider = Preconditions.checkNotNull(bufferProvider);
-            return this;
-        }
+        /**
+         * A Builder for {@link AudioSource.Settings}
+         */
+        @AutoValue.Builder
+        public abstract static class Builder {
+            /**
+             * Sets the device audio source.
+             *
+             * @see android.media.MediaRecorder.AudioSource#MIC
+             * @see android.media.MediaRecorder.AudioSource#CAMCORDER
+             */
+            @NonNull
+            public abstract Builder setAudioSource(int audioSource);
 
-        /** Build the AudioSource. */
-        @RequiresPermission(Manifest.permission.RECORD_AUDIO)
-        @NonNull
-        public AudioSource build() throws AudioSourceAccessException {
-            String missing = "";
-            if (mExecutor == null) {
-                missing += " executor";
+            /**
+             * Sets the audio sample rate in Hertz.
+             */
+            @NonNull
+            public abstract Builder setSampleRate(@IntRange(from = 1) int sampleRate);
+
+            /**
+             * Sets the channel count.
+             */
+            @NonNull
+            public abstract Builder setChannelCount(@IntRange(from = 1) int channelCount);
+
+            /**
+             * Sets the audio format.
+             *
+             * @see AudioFormat#ENCODING_PCM_16BIT
+             */
+            @NonNull
+            public abstract Builder setAudioFormat(int audioFormat);
+
+            abstract Settings autoBuild(); // Actual build method. Not public.
+
+            /**
+             * Returns the built config after performing settings validation.
+             *
+             * <p>It should be verified that combination of sample rate, channel count and audio
+             * format is supported by {@link AudioSource#isSettingsSupported(int, int, int)} or
+             * an {@link UnsupportedOperationException} will be thrown when passing the settings
+             * to the
+             * {@linkplain AudioSource#AudioSource(Settings, Executor, BufferProvider) AudioSource
+             * constructor}.
+             *
+             * @throws IllegalArgumentException if a setting is missing or invalid.
+             */
+            @NonNull
+            public final Settings build() {
+                Settings settings = autoBuild();
+                String missingOrInvalid = "";
+                if (settings.getAudioSource() == -1) {
+                    missingOrInvalid += " audioSource";
+                }
+                if (settings.getSampleRate() <= 0) {
+                    missingOrInvalid += " sampleRate";
+                }
+                if (settings.getChannelCount() <= 0) {
+                    missingOrInvalid += " channelCount";
+                }
+                if (settings.getAudioFormat() == -1) {
+                    missingOrInvalid += " audioFormat";
+                }
+
+                if (!missingOrInvalid.isEmpty()) {
+                    throw new IllegalArgumentException("Required settings missing or "
+                            + "non-positive:" + missingOrInvalid);
+                }
+
+                return settings;
             }
-            if (mBufferProvider == null) {
-                missing += " bufferProvider";
+
+            // Should not be instantiated directly
+            Builder() {
             }
-            if (mAudioSource == -1) {
-                missing += " audioSource";
-            }
-            if (mSampleRate == -1) {
-                missing += " sampleRate";
-            }
-            if (mChannelCount == -1) {
-                missing += " channelCount";
-            }
-            if (mAudioFormat == -1) {
-                missing += " audioFormat";
-            }
-            if (!missing.isEmpty()) {
-                throw new IllegalStateException("Missing required properties:" + missing);
-            }
-            if (!isSettingsSupported(mSampleRate, mChannelCount, mAudioFormat)) {
-                throw new IllegalStateException(String.format("The combination of sample rate %d "
-                                + ", channel count %d and audio format %d is not supported.",
-                        mSampleRate, mChannelCount, mAudioFormat));
-            }
-            return new AudioSource(mExecutor,
-                    mBufferProvider,
-                    mAudioSource,
-                    mSampleRate,
-                    mChannelCount,
-                    mAudioFormat
-            );
         }
     }
 
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
index b3286f1..39bc237 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
@@ -52,8 +52,8 @@
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
-import kotlinx.coroutines.withTimeout
 import org.junit.After
+import org.junit.Assert.assertTrue
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Rule
@@ -223,17 +223,15 @@
 
         val waitingList = mutableListOf<CaptureContainer>()
 
-        suspend fun waitFor(
+        fun waitFor(
             timeout: Long = TimeUnit.SECONDS.toMillis(5),
             numOfCaptures: Int = 1,
             verifyResults: (captureRequests: List<CaptureRequest>) -> Unit
         ) {
             val resultContainer = CaptureContainer(CountDownLatch(numOfCaptures))
             waitingList.add(resultContainer)
-            withTimeout(timeout) {
-                resultContainer.countDownLatch.await()
-                verifyResults(resultContainer.captureRequests)
-            }
+            assertTrue(resultContainer.countDownLatch.await(timeout, TimeUnit.MILLISECONDS))
+            verifyResults(resultContainer.captureRequests)
             waitingList.remove(resultContainer)
         }
 
diff --git a/compose/benchmark-utils/OWNERS b/compose/benchmark-utils/OWNERS
index 305021a..b2c0d56 100644
--- a/compose/benchmark-utils/OWNERS
+++ b/compose/benchmark-utils/OWNERS
@@ -1,2 +1 @@
-pavlis@google.com
 jellefresen@google.com
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyListScrollingBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyListScrollingBenchmark.kt
index 036d159..55964fa 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyListScrollingBenchmark.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyListScrollingBenchmark.kt
@@ -48,6 +48,7 @@
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.Assume
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -83,6 +84,7 @@
         }
     }
 
+    @Ignore("b/206865677")
     @Test
     fun scrollViaPointerInput_noNewItems() {
         benchmarkRule.toggleStateBenchmark {
@@ -95,6 +97,7 @@
         }
     }
 
+    @Ignore("b/206865677")
     @Test
     fun scrollViaPointerInput_newItemComposed() {
         benchmarkRule.toggleStateBenchmark {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt
index 073fad1..3fb977b 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation
 
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.gestures.TransformableState
 import androidx.compose.foundation.gestures.rememberTransformableState
 import androidx.compose.foundation.gestures.animatePanBy
@@ -29,7 +30,9 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.InspectableValue
@@ -37,6 +40,7 @@
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
@@ -47,6 +51,9 @@
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import org.junit.After
 import org.junit.Before
@@ -66,6 +73,8 @@
     @get:Rule
     val rule = createComposeRule()
 
+    private lateinit var scope: CoroutineScope
+
     @Before
     fun before() {
         isDebugInspectorInfoEnabled = true
@@ -76,6 +85,14 @@
         isDebugInspectorInfoEnabled = false
     }
 
+    private fun ComposeContentTestRule.setContentAndGetScope(content: @Composable () -> Unit) {
+        setContent {
+            val actualScope = rememberCoroutineScope()
+            SideEffect { scope = actualScope }
+            content()
+        }
+    }
+
     @Test
     fun transformable_zoomIn() {
         var cumulativeScale = 1.0f
@@ -564,6 +581,36 @@
     }
 
     @Test
+    fun transformable_animateCancelledUpdatesIsTransformInProgress() {
+        rule.mainClock.autoAdvance = false
+        val state = TransformableState { _, _, _ -> }
+        setTransformableContent {
+            Modifier.transformable(state)
+        }
+
+        lateinit var animateJob: Job
+
+        rule.runOnIdle {
+            assertThat(state.isTransformInProgress).isFalse()
+            animateJob = scope.launch {
+                state.animateZoomBy(4f, tween(1000))
+            }
+        }
+
+        rule.mainClock.advanceTimeBy(500)
+
+        rule.runOnIdle {
+            assertThat(state.isTransformInProgress).isTrue()
+        }
+
+        animateJob.cancel()
+
+        rule.runOnIdle {
+            assertThat(state.isTransformInProgress).isFalse()
+        }
+    }
+
+    @Test
     fun testInspectorValue() {
         rule.setContent {
             val state = rememberTransformableState { _, _, _ -> }
@@ -579,7 +626,7 @@
     }
 
     private fun setTransformableContent(getModifier: @Composable () -> Modifier) {
-        rule.setContent {
+        rule.setContentAndGetScope {
             Box(Modifier.size(600.dp).testTag(TEST_TAG).then(getModifier()))
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/TempListUtils.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/TempListUtils.kt
index 0d55ea2..34d6bae 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/TempListUtils.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/TempListUtils.kt
@@ -25,6 +25,10 @@
 
 /**
  * Returns a list containing only elements matching the given [predicate].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
@@ -42,6 +46,10 @@
  *
  * Returns the specified [initial] value if the collection is empty.
  *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
+ *
  * @param [operation] function that takes current accumulator value and an element, and calculates the next accumulator value.
  */
 @OptIn(ExperimentalContracts::class)
@@ -57,6 +65,10 @@
 /**
  * Returns a list containing the results of applying the given [transform] function
  * to each element in the original collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R> List<T>.fastMapIndexedNotNull(
@@ -73,6 +85,10 @@
 /**
  * Returns the largest value among all values produced by selector function applied to each element
  * in the collection or null if there are no elements.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R : Comparable<R>> List<T>.fastMaxOfOrNull(selector: (T) -> R): R? {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt
index 5298664..1e5d2b7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TransformableState.kt
@@ -245,8 +245,11 @@
     ): Unit = coroutineScope {
         transformMutex.mutateWith(transformScope, transformPriority) {
             isTransformingState.value = true
-            block()
-            isTransformingState.value = false
+            try {
+                block()
+            } finally {
+                isTransformingState.value = false
+            }
         }
     }
 
diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
index 2a9e88f..3ecc5c7 100644
--- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
+++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
@@ -33,13 +33,11 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.input.mouse.MouseScrollOrientation
-import androidx.compose.ui.input.mouse.MouseScrollUnit
-import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.InternalTestApi
@@ -432,10 +430,10 @@
 
     @OptIn(InternalTestApi::class, ExperimentalComposeUiApi::class)
     private fun ComposeTestRule.performMouseScroll(x: Int, y: Int, delta: Float) {
-        (this as DesktopComposeTestRule).scene.sendPointerScrollEvent(
+        (this as DesktopComposeTestRule).scene.sendPointerEvent(
+            PointerEventType.Move,
             Offset(x.toFloat(), y.toFloat()),
-            MouseScrollUnit.Line(delta),
-            MouseScrollOrientation.Vertical
+            scrollDelta = Offset(x = 0f, y = delta)
         )
     }
 
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt
index 844d010..c225449 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/TapGestureDetectorTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.gestures
 
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.input.pointer.consumeDownChange
 import androidx.compose.ui.input.pointer.consumePositionChange
 import kotlinx.coroutines.delay
@@ -108,10 +109,12 @@
     /**
      * Clicking in the region should result in the callback being invoked.
      */
+    @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun normalTap() = util.executeInComposition {
         val down = down(5f, 5f)
         assertTrue(down.consumed.downChange)
+        assertTrue(down.isConsumed)
 
         assertTrue(pressed)
         assertFalse(tapped)
@@ -119,6 +122,7 @@
 
         val up = down.up(50)
         assertTrue(up.consumed.downChange)
+        assertTrue(up.isConsumed)
 
         assertTrue(tapped)
         assertTrue(released)
@@ -224,6 +228,7 @@
      * Pressing in the region, sliding out and then lifting should result in
      * the callback not being invoked
      */
+    @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun tapMiss() = util.executeInComposition {
         val up = down(5f, 5f)
@@ -235,6 +240,7 @@
         assertFalse(released)
         assertFalse(tapped)
         assertFalse(up.consumed.downChange)
+        assertFalse(up.isConsumed)
     }
 
     /**
@@ -467,9 +473,46 @@
         assertTrue(canceled)
     }
 
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun consumedChange_MotionTap() = util.executeInComposition {
+        down(5f, 5f)
+            .moveTo(6f, 2f) {
+                consume()
+            }
+            .up(50)
+
+        assertFalse(tapped)
+        assertTrue(pressed)
+        assertFalse(released)
+        assertTrue(canceled)
+    }
+
+    /**
+     * Clicking in the region with the up already consumed should result in the callback not
+     * being invoked.
+     */
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun consumedChange_upTap() = util.executeInComposition {
+        val down = down(5f, 5f)
+
+        assertFalse(tapped)
+        assertTrue(pressed)
+
+        down.up {
+            consume()
+        }
+
+        assertFalse(tapped)
+        assertFalse(released)
+        assertTrue(canceled)
+    }
+
     /**
      * Ensure that two-finger taps work.
      */
+    @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun twoFingerTap() = util.executeInComposition {
         val down = down(1f, 1f)
@@ -480,16 +523,19 @@
 
         val down2 = down(9f, 5f)
         assertFalse(down2.consumed.downChange)
+        assertFalse(down2.isConsumed)
 
         assertFalse(pressed)
 
         val up = down.up()
         assertFalse(up.consumed.downChange)
+        assertFalse(up.isConsumed)
         assertFalse(tapped)
         assertFalse(released)
 
         val up2 = down2.up()
         assertTrue(up2.consumed.downChange)
+        assertTrue(up2.isConsumed)
 
         assertTrue(tapped)
         assertTrue(released)
diff --git a/compose/integration-tests/demos/OWNERS b/compose/integration-tests/demos/OWNERS
index 49be039..cb0413de 100644
--- a/compose/integration-tests/demos/OWNERS
+++ b/compose/integration-tests/demos/OWNERS
@@ -1,4 +1,3 @@
-pavlis@google.com
 adamp@google.com
 mount@google.com
 popam@google.com
diff --git a/compose/integration-tests/demos/build.gradle b/compose/integration-tests/demos/build.gradle
index 00e53eb..cec8a8f 100644
--- a/compose/integration-tests/demos/build.gradle
+++ b/compose/integration-tests/demos/build.gradle
@@ -22,6 +22,7 @@
     implementation(project(":compose:foundation:foundation-layout"))
     implementation(project(":compose:integration-tests:demos:common"))
     implementation(project(":compose:material:material"))
+    implementation(project(":compose:material3:material3"))
     implementation(project(":compose:runtime:runtime"))
     implementation(project(":compose:ui:ui"))
 
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoActivity.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoActivity.kt
index b438f13..0fa2230 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoActivity.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoActivity.kt
@@ -17,22 +17,23 @@
 package androidx.compose.integration.demos
 
 import android.app.Activity
-import android.content.Context
 import android.content.Intent
-import android.content.SharedPreferences
 import android.os.Bundle
 import android.view.View
 import android.view.Window
 import androidx.activity.OnBackPressedCallback
 import androidx.activity.OnBackPressedDispatcher
+import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.integration.demos.common.ActivityDemo
 import androidx.compose.integration.demos.common.Demo
 import androidx.compose.integration.demos.common.DemoCategory
-import androidx.compose.material.Colors
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.darkColors
-import androidx.compose.material.lightColors
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -45,12 +46,12 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.LocalView
 import androidx.fragment.app.FragmentActivity
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
-import androidx.preference.PreferenceManager
 
 /**
  * Main [Activity] containing all Compose related demos.
@@ -75,18 +76,19 @@
             ) {
                 Navigator(AllDemosCategory, onBackPressedDispatcher, activityStarter)
             }
-            val demoColors = remember {
-                DemoColors().also {
-                    lifecycle.addObserver(
-                        LifecycleEventObserver { _, event ->
-                            if (event == Lifecycle.Event.ON_RESUME) {
-                                it.loadColorsFromSharedPreferences(this)
-                            }
-                        }
-                    )
+            val isDynamicThemeOn = remember { mutableStateOf(IsDynamicThemingAvailable) }
+            DisposableEffect(lifecycle) {
+                val obs = LifecycleEventObserver { _, event ->
+                    if (event == Lifecycle.Event.ON_RESUME) {
+                        isDynamicThemeOn.value = isDynamicThemeSettingOn(applicationContext)
+                    }
+                }
+                lifecycle.addObserver(obs)
+                onDispose {
+                    lifecycle.removeObserver(obs)
                 }
             }
-            DemoTheme(demoColors, window) {
+            DemoTheme(isDynamicThemeOn.value, window) {
                 val filteringMode = rememberSaveable(
                     saver = FilterMode.Saver(onBackPressedDispatcher)
                 ) {
@@ -122,19 +124,26 @@
 
 @Composable
 private fun DemoTheme(
-    demoColors: DemoColors,
+    isDynamicThemeOn: Boolean,
     window: Window,
     content: @Composable () -> Unit
 ) {
-    MaterialTheme(demoColors.colors) {
-        val statusBarColor = with(MaterialTheme.colors) {
-            (if (isLight) primaryVariant else Color.Black).toArgb()
+    val isDarkMode = isSystemInDarkTheme()
+
+    @Suppress("NewApi")
+    val colorScheme =
+        if (isDynamicThemeOn) {
+            val context = LocalContext.current
+            if (isDarkMode) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+        } else {
+            if (isDarkMode) darkColorScheme() else lightColorScheme()
         }
-        SideEffect {
-            window.statusBarColor = statusBarColor
-        }
-        content()
+
+    SideEffect {
+        window.statusBarColor =
+            (if (isDarkMode) Color.Black else colorScheme.inversePrimary).toArgb()
     }
+    MaterialTheme(colorScheme = colorScheme, content = content)
 }
 
 private class Navigator private constructor(
@@ -254,95 +263,3 @@
         )
     }
 }
-
-/**
- * Returns a [DemoColors] from the values saved to [SharedPreferences]. If a given color is
- * not present in the [SharedPreferences], its default value as defined in [Colors]
- * will be returned.
- */
-fun DemoColors.loadColorsFromSharedPreferences(context: Context) {
-    val sharedPreferences =
-        PreferenceManager.getDefaultSharedPreferences(context)
-
-    fun getColorsFromSharedPreferences(isLightTheme: Boolean): Colors {
-        val function = if (isLightTheme) ::reflectLightColors else ::reflectDarkColors
-        val parametersToSet = function.parameters.mapNotNull { parameter ->
-            val savedValue = sharedPreferences.getString(parameter.name + isLightTheme, "")
-            if (savedValue.isNullOrBlank()) {
-                null
-            } else {
-                // TODO: should be a Color(savedValue.toLong(16)) when b/154329050 is fixed
-                val parsedColor = savedValue.toLong(16)
-                parameter to parsedColor
-            }
-        }.toMap()
-        return function.callBy(parametersToSet)
-    }
-
-    light = getColorsFromSharedPreferences(true)
-    dark = getColorsFromSharedPreferences(false)
-}
-
-/**
- * TODO: remove after b/154329050 is fixed
- * Inline classes don't play well with reflection, so we want boxed classes for our
- * call to [lightColors].
- */
-internal fun reflectLightColors(
-    primary: Long = 0xFF6200EE,
-    primaryVariant: Long = 0xFF3700B3,
-    secondary: Long = 0xFF03DAC6,
-    secondaryVariant: Long = 0xFF018786,
-    background: Long = 0xFFFFFFFF,
-    surface: Long = 0xFFFFFFFF,
-    error: Long = 0xFFB00020,
-    onPrimary: Long = 0xFFFFFFFF,
-    onSecondary: Long = 0xFF000000,
-    onBackground: Long = 0xFF000000,
-    onSurface: Long = 0xFF000000,
-    onError: Long = 0xFFFFFFFF
-) = lightColors(
-    primary = Color(primary),
-    primaryVariant = Color(primaryVariant),
-    secondary = Color(secondary),
-    secondaryVariant = Color(secondaryVariant),
-    background = Color(background),
-    surface = Color(surface),
-    error = Color(error),
-    onPrimary = Color(onPrimary),
-    onSecondary = Color(onSecondary),
-    onBackground = Color(onBackground),
-    onSurface = Color(onSurface),
-    onError = Color(onError)
-)
-
-/**
- * TODO: remove after b/154329050 is fixed
- * Inline classes don't play well with reflection, so we want boxed classes for our
- * call to [darkColors].
- */
-internal fun reflectDarkColors(
-    primary: Long = 0xFFBB86FC,
-    primaryVariant: Long = 0xFF3700B3,
-    secondary: Long = 0xFF03DAC6,
-    background: Long = 0xFF121212,
-    surface: Long = 0xFF121212,
-    error: Long = 0xFFCF6679,
-    onPrimary: Long = 0xFF000000,
-    onSecondary: Long = 0xFF000000,
-    onBackground: Long = 0xFFFFFFFF,
-    onSurface: Long = 0xFFFFFFFF,
-    onError: Long = 0xFF000000
-) = darkColors(
-    primary = Color(primary),
-    primaryVariant = Color(primaryVariant),
-    secondary = Color(secondary),
-    background = Color(background),
-    surface = Color(surface),
-    error = Color(error),
-    onPrimary = Color(onPrimary),
-    onSecondary = Color(onSecondary),
-    onBackground = Color(onBackground),
-    onSurface = Color(onSurface),
-    onError = Color(onError)
-)
\ No newline at end of file
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
index 858fa8d..902fe64 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoApp.kt
@@ -18,10 +18,15 @@
 
 import androidx.compose.animation.Crossfade
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
@@ -31,28 +36,31 @@
 import androidx.compose.integration.demos.common.DemoCategory
 import androidx.compose.integration.demos.common.FragmentDemo
 import androidx.compose.integration.demos.common.allLaunchableDemos
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.ListItem
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Scaffold
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.material.icons.filled.ArrowForward
 import androidx.compose.material.icons.filled.Search
 import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SmallTopAppBar
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.unit.LayoutDirection
@@ -61,6 +69,7 @@
 import androidx.fragment.app.FragmentActivity
 import androidx.fragment.app.FragmentContainerView
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun DemoApp(
     currentDemo: Demo,
@@ -77,11 +86,14 @@
 
     var filterText by rememberSaveable { mutableStateOf("") }
 
+    val scrollBehavior = remember { TopAppBarDefaults.pinnedScrollBehavior() }
+
     Scaffold(
         topBar = {
             DemoAppBar(
                 title = backStackTitle,
-                navigationIcon = navigationIcon,
+                scrollBehavior = scrollBehavior,
+                navigationIcon = navigationIcon ?: {},
                 launchSettings = launchSettings,
                 isFiltering = isFiltering,
                 filterText = filterText,
@@ -89,7 +101,8 @@
                 onStartFiltering = onStartFiltering,
                 onEndFiltering = onEndFiltering
             )
-        }
+        },
+        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
     ) { innerPadding ->
         val modifier = Modifier.padding(innerPadding)
         DemoContent(modifier, currentDemo, isFiltering, filterText, onNavigateToDemo, onNavigateUp)
@@ -106,7 +119,7 @@
     onNavigateUp: () -> Unit
 ) {
     Crossfade(isFiltering to currentDemo) { (filtering, demo) ->
-        Surface(modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
+        Surface(modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
             if (filtering) {
                 DemoFilter(
                     launchableDemos = AllDemosCategory.allLaunchableDemos(),
@@ -121,12 +134,26 @@
 }
 
 @Composable
+fun Material2LegacyTheme(content: @Composable () -> Unit) {
+    val material2Colors =
+        if (isSystemInDarkTheme()) {
+            androidx.compose.material.darkColors()
+        } else {
+            androidx.compose.material.lightColors()
+        }
+    androidx.compose.material.MaterialTheme(colors = material2Colors, content = content)
+}
+
+@Composable
 private fun DisplayDemo(demo: Demo, onNavigate: (Demo) -> Unit, onNavigateUp: () -> Unit) {
     when (demo) {
         is ActivityDemo<*> -> {
             /* should never get here as activity demos are not added to the backstack*/
         }
-        is ComposableDemo -> demo.content(onNavigateUp)
+        is ComposableDemo ->
+            // provide material 2 as well for interop, find a way to
+            // remove it when all demos migrated to m3
+            Material2LegacyTheme { demo.content(onNavigateUp) }
         is DemoCategory -> DisplayDemoCategory(demo, onNavigate)
         is FragmentDemo<*> -> {
             lateinit var view: FragmentContainerView
@@ -156,21 +183,16 @@
 }
 
 @Composable
-@OptIn(ExperimentalMaterialApi::class)
 private fun DisplayDemoCategory(category: DemoCategory, onNavigate: (Demo) -> Unit) {
-    // TODO: migrate to LazyColumn after b/175671850
+    // TODO: migrate to LazyColumn after DemoTests are rewritten to accommodate laziness
     Column(Modifier.verticalScroll(rememberScrollState())) {
         category.demos.forEach { demo ->
-            ListItem(
-                text = {
-                    Text(
-                        modifier = Modifier.height(56.dp)
-                            .wrapContentSize(Alignment.Center),
-                        text = demo.title
-                    )
-                },
-                modifier = Modifier.clickable { onNavigate(demo) }
-            )
+            ListItem(onClick = { onNavigate(demo) }) {
+                Text(
+                    modifier = Modifier.height(56.dp).wrapContentSize(Alignment.Center),
+                    text = demo.title
+                )
+            }
         }
     }
 }
@@ -179,7 +201,8 @@
 @Composable
 private fun DemoAppBar(
     title: String,
-    navigationIcon: @Composable (() -> Unit)?,
+    scrollBehavior: TopAppBarScrollBehavior,
+    navigationIcon: @Composable () -> Unit,
     isFiltering: Boolean,
     filterText: String,
     onFilter: (String) -> Unit,
@@ -191,13 +214,15 @@
         FilterAppBar(
             filterText = filterText,
             onFilter = onFilter,
-            onClose = onEndFiltering
+            onClose = onEndFiltering,
+            scrollBehavior = scrollBehavior
         )
     } else {
-        TopAppBar(
+        SmallTopAppBar(
             title = {
                 Text(title, Modifier.testTag(Tags.AppBarTitle))
             },
+            scrollBehavior = scrollBehavior,
             navigationIcon = navigationIcon,
             actions = {
                 AppBarIcons.Filter(onClick = onStartFiltering)
@@ -233,3 +258,20 @@
         }
     }
 }
+
+@Composable
+internal fun ListItem(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    content: @Composable (() -> Unit)
+) {
+    Box(
+        modifier
+            .heightIn(min = 48.dp)
+            .fillMaxWidth()
+            .clickable(onClick = onClick)
+            .padding(horizontal = 16.dp)
+            .wrapContentHeight(Alignment.CenterVertically),
+        contentAlignment = Alignment.CenterStart
+    ) { content() }
+}
\ No newline at end of file
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoColors.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoColors.kt
deleted file mode 100644
index 5bb3529..0000000
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoColors.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2020 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.integration.demos
-
-import android.content.SharedPreferences
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.Colors
-import androidx.compose.material.darkColors
-import androidx.compose.material.lightColors
-
-/**
- * Wrapper class that contains a light and dark [Colors], to allow saving and
- * restoring the entire light / dark theme to and from [SharedPreferences].
- */
-@Stable
-class DemoColors {
-    var light: Colors by mutableStateOf(lightColors())
-    var dark: Colors by mutableStateOf(darkColors())
-
-    val colors
-        @Composable get() = if (isSystemInDarkTheme()) dark else light
-}
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt
index 957232c..62a05db 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoFilter.kt
@@ -17,7 +17,6 @@
 package androidx.compose.integration.demos
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
@@ -26,17 +25,16 @@
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.integration.demos.common.Demo
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.ListItem
-import androidx.compose.material.LocalContentColor
-import androidx.compose.material.LocalTextStyle
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.material.TopAppBar
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SmallTopAppBar
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarScrollBehavior
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.key
@@ -46,7 +44,6 @@
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.compositeOver
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.buildAnnotatedString
 import androidx.compose.ui.text.withStyle
@@ -73,33 +70,30 @@
 }
 
 /**
- * [TopAppBar] with a text field allowing filtering all the demos.
+ * [SmallTopAppBar] with a text field allowing filtering all the demos.
  */
 @Composable
 fun FilterAppBar(
     filterText: String,
     onFilter: (String) -> Unit,
-    onClose: () -> Unit
+    onClose: () -> Unit,
+    scrollBehavior: TopAppBarScrollBehavior
 ) {
-    with(MaterialTheme.colors) {
-        val appBarColor = if (isLight) {
-            surface
-        } else {
-            // Blending primary over surface according to Material design guidance for brand
-            // surfaces in dark theme
-            primary.copy(alpha = 0.08f).compositeOver(surface)
-        }
-        TopAppBar(backgroundColor = appBarColor, contentColor = onSurface) {
-            IconButton(modifier = Modifier.align(Alignment.CenterVertically), onClick = onClose) {
+    SmallTopAppBar(
+        navigationIcon = {
+            IconButton(onClick = onClose) {
                 Icon(Icons.Filled.Close, null)
             }
+        },
+        title = {
             FilterField(
                 filterText,
                 onFilter,
-                Modifier.fillMaxWidth().align(Alignment.CenterVertically)
+                Modifier.fillMaxWidth()
             )
-        }
-    }
+        },
+        scrollBehavior = scrollBehavior
+    )
 }
 
 /**
@@ -131,13 +125,12 @@
  * [ListItem] that displays a [demo] and highlights any matches for [filterText] inside [Demo.title]
  */
 @Composable
-@OptIn(ExperimentalMaterialApi::class)
 private fun FilteredDemoListItem(
     demo: Demo,
     filterText: String,
     onNavigate: (Demo) -> Unit
 ) {
-    val primary = MaterialTheme.colors.primary
+    val primary = MaterialTheme.colorScheme.primary
     val annotatedString = buildAnnotatedString {
         val title = demo.title
         var currentIndex = 0
@@ -159,13 +152,11 @@
     }
     key(demo.title) {
         ListItem(
-            text = {
-                Text(
-                    modifier = Modifier.height(56.dp).wrapContentSize(Alignment.Center),
-                    text = annotatedString
-                )
-            },
-            modifier = Modifier.clickable { onNavigate(demo) }
-        )
+            onClick = { onNavigate(demo) }) {
+            Text(
+                modifier = Modifier.height(56.dp).wrapContentSize(Alignment.Center),
+                text = annotatedString
+            )
+        }
     }
 }
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoSettingsActivity.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoSettingsActivity.kt
index 3f0f540..e41e89c 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoSettingsActivity.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/DemoSettingsActivity.kt
@@ -17,21 +17,15 @@
 package androidx.compose.integration.demos
 
 import android.content.Context
-import android.content.SharedPreferences
+import android.os.Build
 import android.os.Bundle
 import androidx.appcompat.app.AppCompatActivity
-import androidx.preference.EditTextPreference
-import androidx.preference.Preference
+import androidx.compose.integration.demos.DemoSettingsActivity.SettingsFragment
+import androidx.preference.CheckBoxPreference
 import androidx.preference.PreferenceCategory
 import androidx.preference.PreferenceFragmentCompat
 import androidx.preference.PreferenceManager
 import androidx.preference.plusAssign
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.material.Colors
-import androidx.compose.material.darkColors
-import androidx.compose.material.lightColors
-import kotlin.reflect.full.memberProperties
 
 /**
  * Shell [AppCompatActivity] around [SettingsFragment], as we need a FragmentActivity subclass
@@ -56,371 +50,22 @@
                 screen += this
             }
 
-            general += Preference(context).apply {
-                title = "Shuffle all colors"
-                onPreferenceClickListener = Preference.OnPreferenceClickListener {
-                    generateRandomPalette().saveColors(context)
-                    requireActivity().finish()
-                    true
-                }
-            }
-
-            general += Preference(context).apply {
-                title = "Reset colors to default"
-                onPreferenceClickListener = Preference.OnPreferenceClickListener {
-                    val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
-                    sharedPreferences.edit().clear().apply()
-                    requireActivity().finish()
-                    true
-                }
-            }
-
-            val light = PreferenceCategory(context).apply {
-                title = "Light colors"
-                screen += this
-            }
-            // Create new Colors to resolve defaults
-            lightColors().forEachColorProperty { name, color ->
-                light += EditTextPreference(context).apply {
-                    key = name + true
-                    title = name
-                    // set the default value to be the default for Colors
-                    setDefaultValue(Integer.toHexString(color.toArgb()))
-                    summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
-                }
-            }
-
-            val dark = PreferenceCategory(context).apply {
-                title = "Dark colors"
-                screen += this
-            }
-
-            darkColors().forEachColorProperty { name, color ->
-                dark += EditTextPreference(context).apply {
-                    key = name + false
-                    title = name
-                    // set the default value to be the default for Colors
-                    setDefaultValue(Integer.toHexString(color.toArgb()))
-                    summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
-                }
+            general += CheckBoxPreference(context).apply {
+                title = "Dynamic theming (android S+)"
+                isEnabled = IsDynamicThemingAvailable
+                key = IsDynamicThemeOnKey
+                setDefaultValue(isDynamicThemeSettingOn(context))
             }
             preferenceScreen = screen
         }
     }
 }
 
-/**
- * Iterates over each color present in a given [Colors].
- *
- * @param action the action to take on each property, where name is the name of the property,
- * such as 'primary' for [Colors.primary], and color is the resolved [Color] of the
- * property.
- */
-private fun Colors.forEachColorProperty(action: (name: String, color: Color) -> Unit) {
-    Colors::class.memberProperties.forEach { property ->
-        val name = property.name
-        val color = property.get(this) as? Color ?: return@forEach
-        action(name, color)
-    }
+internal fun isDynamicThemeSettingOn(context: Context): Boolean {
+    return PreferenceManager
+        .getDefaultSharedPreferences(context)
+        .getBoolean(IsDynamicThemeOnKey, IsDynamicThemingAvailable)
 }
 
-/**
- * Persists the current [DemoColors] to [SharedPreferences].
- */
-private fun DemoColors.saveColors(context: Context) {
-    light.forEachColorProperty { name, color ->
-        PreferenceManager.getDefaultSharedPreferences(context)
-            .edit()
-            .putString(name + true, Integer.toHexString(color.toArgb()))
-            .apply()
-    }
-    dark.forEachColorProperty { name, color ->
-        PreferenceManager.getDefaultSharedPreferences(context)
-            .edit()
-            .putString(name + false, Integer.toHexString(color.toArgb()))
-            .apply()
-    }
-}
-
-/**
- * Generates a [DemoColors] with random [Colors.primary], [Colors.onPrimary],
- * [Colors.secondary] and [Colors.onSecondary] as random dark-on-light or light-on-dark
- * pairs. Other colors are kept from the baseline.
- */
-private fun generateRandomPalette(): DemoColors {
-    return DemoColors().apply {
-        val (lightPrimary, lightOnPrimary) = generateColorPair(true)
-        val (lightSecondary, lightOnSecondary) = generateColorPair(true)
-        light = lightColors(
-            primary = lightPrimary,
-            secondary = lightSecondary,
-            onPrimary = lightOnPrimary,
-            onSecondary = lightOnSecondary
-        )
-        val (darkPrimary, darkOnPrimary) = generateColorPair(false)
-        val (darkSecondary, darkOnSecondary) = generateColorPair(false)
-        dark = darkColors(
-            primary = darkPrimary,
-            secondary = darkSecondary,
-            onPrimary = darkOnPrimary,
-            onSecondary = darkOnSecondary
-        )
-    }
-}
-
-/**
- * Generate a random dark and light color from the palette, and returns either a dark-on-light
- * or light-on-dark color pair.
- */
-private fun generateColorPair(isLightTheme: Boolean): Pair<Color, Color> {
-    val darkColor = Color(DarkPaletteColors.random())
-    val lightColor = Color(LightPaletteColors.random())
-    return if (isLightTheme) {
-        darkColor to lightColor
-    } else {
-        lightColor to darkColor
-    }
-}
-
-// Colors taken from https://material.io/design/color -> 2014 Material Design color palettes
-val LightPaletteColors = listOf(
-    0xFFEF5350,
-    0xFFF44336,
-    0xFFE53935,
-    0xFFD32F2F,
-    0xFFC62828,
-    0xFFB71C1C,
-    0xFFFF5252,
-    0xFFFF1744,
-    0xFFD50000,
-    0xFFEC407A,
-    0xFFE91E63,
-    0xFFD81B60,
-    0xFFC2185B,
-    0xFFAD1457,
-    0xFF880E4F,
-    0xFFFF4081,
-    0xFFF50057,
-    0xFFC51162,
-    0xFFBA68C8,
-    0xFFAB47BC,
-    0xFF9C27B0,
-    0xFF8E24AA,
-    0xFF7B1FA2,
-    0xFF6A1B9A,
-    0xFF4A148C,
-    0xFFE040FB,
-    0xFFD500F9,
-    0xFFAA00FF,
-    0xFF9575CD,
-    0xFF7E57C2,
-    0xFF673AB7,
-    0xFF5E35B1,
-    0xFF512DA8,
-    0xFF4527A0,
-    0xFF311B92,
-    0xFF7C4DFF,
-    0xFF651FFF,
-    0xFF6200EA,
-    0xFF7986CB,
-    0xFF5C6BC0,
-    0xFF3F51B5,
-    0xFF3949AB,
-    0xFF303F9F,
-    0xFF283593,
-    0xFF1A237E,
-    0xFF536DFE,
-    0xFF3D5AFE,
-    0xFF304FFE,
-    0xFF1E88E5,
-    0xFF1976D2,
-    0xFF1565C0,
-    0xFF0D47A1,
-    0xFF448AFF,
-    0xFF2979FF,
-    0xFF2962FF,
-    0xFF0288D1,
-    0xFF0277BD,
-    0xFF01579B,
-    0xFF0091EA,
-    0xFF0097A7,
-    0xFF00838F,
-    0xFF006064,
-    0xFF009688,
-    0xFF00897B,
-    0xFF00796B,
-    0xFF00695C,
-    0xFF004D40,
-    0xFF43A047,
-    0xFF388E3C,
-    0xFF2E7D32,
-    0xFF1B5E20,
-    0xFF558B2F,
-    0xFF33691E,
-    0xFF827717,
-    0xFFE65100,
-    0xFFF4511E,
-    0xFFE64A19,
-    0xFFD84315,
-    0xFFBF360C,
-    0xFFFF3D00,
-    0xFFDD2C00,
-    0xFFA1887F,
-    0xFF8D6E63,
-    0xFF795548,
-    0xFF6D4C41,
-    0xFF5D4037,
-    0xFF4E342E,
-    0xFF3E2723,
-    0xFF757575,
-    0xFF616161,
-    0xFF424242,
-    0xFF212121,
-    0xFF78909C,
-    0xFF607D8B,
-    0xFF546E7A,
-    0xFF455A64,
-    0xFF37474F,
-    0xFF263238
-)
-
-val DarkPaletteColors = listOf(
-    0xFFFFCDD2,
-    0xFFEF9A9A,
-    0xFFE57373,
-    0xFFFF8A80,
-    0xFFF8BBD0,
-    0xFFF48FB1,
-    0xFFF06292,
-    0xFFFF80AB,
-    0xFFE1BEE7,
-    0xFFCE93D8,
-    0xFFEA80FC,
-    0xFFD1C4E9,
-    0xFFB39DDB,
-    0xFFB388FF,
-    0xFFC5CAE9,
-    0xFF9FA8DA,
-    0xFF8C9EFF,
-    0xFFBBDEFB,
-    0xFF90CAF9,
-    0xFF64B5F6,
-    0xFF42A5F5,
-    0xFF2196F3,
-    0xFF82B1FF,
-    0xFFB3E5FC,
-    0xFF81D4FA,
-    0xFF4FC3F7,
-    0xFF29B6F6,
-    0xFF03A9F4,
-    0xFF039BE5,
-    0xFF80D8FF,
-    0xFF40C4FF,
-    0xFF00B0FF,
-    0xFFB2EBF2,
-    0xFF80DEEA,
-    0xFF4DD0E1,
-    0xFF26C6DA,
-    0xFF00BCD4,
-    0xFF00ACC1,
-    0xFF84FFFF,
-    0xFF18FFFF,
-    0xFF00E5FF,
-    0xFF00B8D4,
-    0xFFB2DFDB,
-    0xFF80CBC4,
-    0xFF4DB6AC,
-    0xFF26A69A,
-    0xFFA7FFEB,
-    0xFF64FFDA,
-    0xFF1DE9B6,
-    0xFF00BFA5,
-    0xFFC8E6C9,
-    0xFFA5D6A7,
-    0xFF81C784,
-    0xFF66BB6A,
-    0xFF4CAF50,
-    0xFFB9F6CA,
-    0xFF69F0AE,
-    0xFF00E676,
-    0xFF00C853,
-    0xFFDCEDC8,
-    0xFFC5E1A5,
-    0xFFAED581,
-    0xFF9CCC65,
-    0xFF8BC34A,
-    0xFF7CB342,
-    0xFF689F38,
-    0xFFCCFF90,
-    0xFFB2FF59,
-    0xFF76FF03,
-    0xFF64DD17,
-    0xFFF0F4C3,
-    0xFFE6EE9C,
-    0xFFDCE775,
-    0xFFD4E157,
-    0xFFCDDC39,
-    0xFFC0CA33,
-    0xFFAFB42B,
-    0xFF9E9D24,
-    0xFFF4FF81,
-    0xFFEEFF41,
-    0xFFC6FF00,
-    0xFFAEEA00,
-    0xFFFFF9C4,
-    0xFFFFF59D,
-    0xFFFFF176,
-    0xFFFFEE58,
-    0xFFFFEB3B,
-    0xFFFDD835,
-    0xFFFBC02D,
-    0xFFF9A825,
-    0xFFF57F17,
-    0xFFFFFF8D,
-    0xFFFFFF00,
-    0xFFFFEA00,
-    0xFFFFD600,
-    0xFFFFECB3,
-    0xFFFFE082,
-    0xFFFFD54F,
-    0xFFFFCA28,
-    0xFFFFC107,
-    0xFFFFB300,
-    0xFFFFA000,
-    0xFFFF8F00,
-    0xFFFF6F00,
-    0xFFFFE57F,
-    0xFFFFD740,
-    0xFFFFC400,
-    0xFFFFAB00,
-    0xFFFFE0B2,
-    0xFFFFCC80,
-    0xFFFFB74D,
-    0xFFFFA726,
-    0xFFFF9800,
-    0xFFFB8C00,
-    0xFFF57C00,
-    0xFFEF6C00,
-    0xFFFFD180,
-    0xFFFFAB40,
-    0xFFFF9100,
-    0xFFFF6D00,
-    0xFFFFCCBC,
-    0xFFFFAB91,
-    0xFFFF8A65,
-    0xFFFF7043,
-    0xFFFF5722,
-    0xFFFF9E80,
-    0xFFFF6E40,
-    0xFFD7CCC8,
-    0xFFBCAAA4,
-    0xFFF5F5F5,
-    0xFFEEEEEE,
-    0xFFE0E0E0,
-    0xFFBDBDBD,
-    0xFF9E9E9E,
-    0xFFCFD8DC,
-    0xFFB0BEC5,
-    0xFF90A4AE,
-    0xFFFFFFFF
-)
\ No newline at end of file
+private const val IsDynamicThemeOnKey = "material3_isDynamicThemeOn"
+internal val IsDynamicThemingAvailable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
\ No newline at end of file
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
index 960a3c3..1ef5eae 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropUi.kt
@@ -34,6 +34,7 @@
 import androidx.appcompat.app.AppCompatActivity
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.Button
 import androidx.compose.material.ButtonDefaults
@@ -43,6 +44,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -181,7 +183,27 @@
     }
 }
 
-private object InteropUiSnippet6 {
+@Composable
+fun InteropUiSnippet6(showCautionIcon: Boolean) {
+    if (showCautionIcon) {
+        CautionIcon(/* ... */)
+    }
+}
+
+@Composable
+fun InteropUiSnippet7() {
+    var isEnabled by rememberSaveable { mutableStateOf(false) }
+
+    Column {
+        ImageWithEnabledOverlay(isEnabled)
+        ControlPanelWithToggle(
+            isEnabled = isEnabled,
+            onEnabledChanged = { isEnabled = it }
+        )
+    }
+}
+
+private object InteropUiSnippet8 {
     @Composable
     fun MyComposable() {
         BoxWithConstraints {
@@ -196,7 +218,7 @@
     }
 }
 
-private object InteropUiSnippet7 {
+private object InteropUiSnippet9 {
     // import androidx.compose.ui.platform.ComposeView
 
     class MyComposeAdapter : RecyclerView.Adapter<MyComposeViewHolder>() {
@@ -233,7 +255,7 @@
     }
 }
 
-private object InteropUiSnippet8 {
+private object InteropUiSnippet10 {
     // import androidx.compose.ui.platform.ViewCompositionStrategy
 
     class MyComposeViewHolder(
@@ -295,6 +317,21 @@
 private fun Icon() {
 }
 
+@Composable
+private fun CautionIcon() {
+}
+
+@Composable
+private fun ImageWithEnabledOverlay(isEnabled: Boolean) {
+}
+
+@Composable
+private fun ControlPanelWithToggle(
+    isEnabled: Boolean,
+    onEnabledChanged: (Boolean) -> Unit
+) {
+}
+
 private class WindowCompat {
     companion object {
         fun setDecorFitsSystemWindows(window: Any, bool: Boolean) {}
diff --git a/compose/lint/internal-lint-checks/build.gradle b/compose/lint/internal-lint-checks/build.gradle
index 412cfb9..3039214 100644
--- a/compose/lint/internal-lint-checks/build.gradle
+++ b/compose/lint/internal-lint-checks/build.gradle
@@ -25,7 +25,6 @@
 dependencies {
     compileOnly(libs.androidLintApi)
     compileOnly(libs.kotlinStdlib)
-    api(project(":lint-checks"))
     implementation(project(":compose:lint:common"))
 
     testImplementation(project(":compose:lint:common-test"))
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
index 89745e0..3bce32a 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
@@ -18,7 +18,6 @@
 
 package androidx.compose.lint
 
-import androidx.build.lint.AndroidXIssueRegistry
 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
@@ -32,7 +31,7 @@
             ListIteratorDetector.ISSUE,
             ModifierInspectorInfoDetector.ISSUE,
             UnnecessaryLambdaCreationDetector.ISSUE,
-        ) + AndroidXIssueRegistry.Issues
+        )
     }
     override val vendor = Vendor(
         vendorName = "Jetpack Compose",
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SliderScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SliderScreenshotTest.kt
index dfbdacc..b282d58 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SliderScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SliderScreenshotTest.kt
@@ -223,4 +223,27 @@
         }
         assertSliderAgainstGolden("rangeSlider_fullRange")
     }
+
+    @Test
+    @ExperimentalMaterialApi
+    fun rangeSliderTest_steps_customColors() {
+        rule.setMaterialContent {
+            Box(wrap.testTag(wrapperTestTag)) {
+                var position by remember { mutableStateOf(30f..70f) }
+                RangeSlider(
+                    values = position,
+                    valueRange = 0f..100f,
+                    onValueChange = { position = it }, steps = 9,
+                    colors = SliderDefaults.colors(
+                        thumbColor = Color.Blue,
+                        activeTrackColor = Color.Red,
+                        inactiveTrackColor = Color.Yellow,
+                        activeTickColor = Color.Magenta,
+                        inactiveTickColor = Color.Cyan
+                    )
+                )
+            }
+        }
+        assertSliderAgainstGolden("rangeSlider_steps_customColors")
+    }
 }
\ No newline at end of file
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
index d7ad38c..4b99271 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
@@ -678,17 +678,18 @@
             trackStrokeWidth,
             StrokeCap.Round
         )
-        tickFractions.groupBy { it > positionFractionEnd }.forEach { (afterFraction, list) ->
-            drawPoints(
-                list.map {
-                    Offset(lerp(sliderStart, sliderEnd, it).x, center.y)
-                },
-                PointMode.Points,
-                (if (afterFraction) inactiveTickColor else activeTickColor).value,
-                trackStrokeWidth,
-                StrokeCap.Round
-            )
-        }
+        tickFractions.groupBy { it > positionFractionEnd || it < positionFractionStart }
+            .forEach { (outsideFraction, list) ->
+                drawPoints(
+                    list.map {
+                        Offset(lerp(sliderStart, sliderEnd, it).x, center.y)
+                    },
+                    PointMode.Points,
+                    (if (outsideFraction) inactiveTickColor else activeTickColor).value,
+                    trackStrokeWidth,
+                    StrokeCap.Round
+                )
+            }
     }
 }
 
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/ListUtils.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/ListUtils.kt
index 2e6554c..cb0552e 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/ListUtils.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/ListUtils.kt
@@ -22,6 +22,10 @@
 /**
  * Iterates through a [List] using the index and calls [action] for each item.
  * This does not allocate an iterator like [Iterable.forEach].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T> List<T>.fastForEach(action: (T) -> Unit) {
@@ -36,6 +40,10 @@
  * Returns a [Set] of all elements.
  *
  * The returned set preserves the element iteration order of the original collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 internal fun <T> List<T>.fastToSet(): Set<T> = HashSet<T>(size).also { set ->
     fastForEach { item -> set.add(item) }
@@ -44,6 +52,10 @@
 /**
  * Iterates through a [List] using the index and calls [action] for each item.
  * This does not allocate an iterator like [Iterable.forEachIndexed].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T> List<T>.fastForEachIndexed(action: (Int, T) -> Unit) {
@@ -57,6 +69,10 @@
 /**
  * Returns a list containing the results of applying the given [transform] function
  * to each element in the original collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R> List<T>.fastMap(transform: (T) -> R): List<R> {
@@ -75,6 +91,10 @@
  * If the collection could be huge, you can specify a non-negative value of [limit], in which case
  * only the first [limit] elements will be appended, followed by the [truncated] string (which
  * defaults to "...").
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 internal fun <T> List<T>.fastJoinToString(
     separator: CharSequence = ", ",
@@ -95,6 +115,10 @@
  * If the collection could be huge, you can specify a non-negative value of [limit], in which
  * case only the first [limit] elements will be appended, followed by the [truncated] string
  * (which defaults to "...").
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 private fun <T, A : Appendable> List<T>.fastJoinTo(
     buffer: A,
@@ -134,6 +158,10 @@
 /**
  * Returns a list containing the results of applying the given [transform] function
  * to each element in the original collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
@@ -149,6 +177,10 @@
  * Returns a list containing only elements matching the given [predicate].
  * @param [predicate] function that takes the index of an element and the element itself
  * and returns the result of predicate evaluation on the element.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T> List<T>.fastFilterIndexed(predicate: (index: Int, T) -> Boolean): List<T> {
diff --git a/compose/test-utils/OWNERS b/compose/test-utils/OWNERS
index 305021a..b2c0d56 100644
--- a/compose/test-utils/OWNERS
+++ b/compose/test-utils/OWNERS
@@ -1,2 +1 @@
-pavlis@google.com
 jellefresen@google.com
diff --git a/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml b/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml
index bc217cf..48da372 100644
--- a/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml
+++ b/compose/ui/ui-inspection/src/androidTest/AndroidManifest.xml
@@ -17,6 +17,7 @@
 
     <application>
         <activity android:name="androidx.compose.ui.inspection.testdata.AndroidViewTestActivity" />
+        <activity android:name="androidx.compose.ui.inspection.testdata.ComposeViewTestActivity" />
         <activity android:name="androidx.compose.ui.inspection.testdata.DialogTestActivity" />
         <activity android:name="androidx.compose.ui.inspection.testdata.ParametersTestActivity" />
         <activity android:name="androidx.compose.ui.inspection.testdata.RippleTestActivity" />
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ComposeViewTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ComposeViewTest.kt
new file mode 100644
index 0000000..9f6366a
--- /dev/null
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ComposeViewTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.inspection
+
+import androidx.compose.ui.inspection.rules.ComposeInspectionRule
+import androidx.compose.ui.inspection.rules.sendCommand
+import androidx.compose.ui.inspection.testdata.ComposeViewTestActivity
+import androidx.compose.ui.inspection.util.GetComposablesCommand
+import androidx.compose.ui.inspection.util.GetParametersCommand
+import androidx.compose.ui.inspection.util.flatten
+import androidx.compose.ui.inspection.util.toMap
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.ComposableNode
+import org.junit.Rule
+import org.junit.Test
+
+@LargeTest
+class ComposeViewTest {
+    @get:Rule
+    val rule = ComposeInspectionRule(ComposeViewTestActivity::class)
+
+    @Test
+    fun composeView(): Unit = runBlocking {
+        val response = rule.inspectorTester.sendCommand(
+            GetComposablesCommand(rule.rootId, skipSystemComposables = false)
+        ).getComposablesResponse
+        val strings = response.stringsList.toMap()
+        val roots = response.rootsList
+        assertThat(roots).hasSize(3)
+        val firstText = roots[0].nodesList.findNode("Text", strings)
+        val secondText = roots[1].nodesList.findNode("Text", strings)
+        val thirdText = roots[2].nodesList.findNode("Text", strings)
+        assertThat(firstText?.textParameter).isEqualTo("one")
+        assertThat(secondText?.textParameter).isEqualTo("two")
+        assertThat(thirdText?.textParameter).isEqualTo("three")
+    }
+
+    private fun Iterable<ComposableNode>.findNode(
+        name: String,
+        strings: Map<Int, String>
+    ): ComposableNode? = flatMap { it.flatten() }.singleOrNull { strings[it.name] == name }
+
+    private val ComposableNode.textParameter: String?
+        get() = runBlocking {
+            val params = rule.inspectorTester.sendCommand(
+                GetParametersCommand(
+                    rule.rootId,
+                    skipSystemComposables = false,
+                    composableId = id
+                )
+            ).getParametersResponse
+            val strings = params.stringsList.toMap()
+            val param = params.parameterGroup.parameterList.single { strings[it.name] == "text" }
+            strings[param.int32Value]
+        }
+}
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/testdata/ComposeViewTestActivity.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/testdata/ComposeViewTestActivity.kt
new file mode 100644
index 0000000..3c36a5a
--- /dev/null
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/testdata/ComposeViewTestActivity.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.inspection.testdata
+
+import android.os.Bundle
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.Text
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.viewinterop.AndroidView
+
+class ComposeViewTestActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            Column {
+                Text("one")
+                AndroidView({ context ->
+                    LinearLayout(context).apply {
+                        orientation = LinearLayout.VERTICAL
+                        addView(TextView(context).apply { text = "AndroidView" })
+                        addView(ComposeView(context).apply {
+                            setContent {
+                                Column {
+                                    Text("two")
+                                    AndroidView({ context ->
+                                        LinearLayout(context).apply {
+                                            orientation = LinearLayout.VERTICAL
+                                            addView(ComposeView(context).apply {
+                                                setContent {
+                                                    Text("three")
+                                                }
+                                            })
+                                        }
+                                    })
+                                }
+                            }
+                        })
+                    }
+                })
+            }
+        }
+    }
+}
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
index 6999d46..af7dab6 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
@@ -27,7 +27,7 @@
 import androidx.compose.ui.inspection.inspector.NodeParameterReference
 import androidx.compose.ui.inspection.proto.StringTable
 import androidx.compose.ui.inspection.proto.convert
-import androidx.compose.ui.inspection.proto.toComposableNodes
+import androidx.compose.ui.inspection.proto.toComposableRoot
 import androidx.compose.ui.inspection.util.ThreadUtils
 import androidx.compose.ui.unit.IntOffset
 import androidx.inspection.Connection
@@ -37,7 +37,6 @@
 import com.google.protobuf.ByteString
 import com.google.protobuf.InvalidProtocolBufferException
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.Command
-import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.ComposableRoot
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetAllParametersCommand
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetAllParametersResponse
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetComposablesCommand
@@ -66,19 +65,31 @@
 
 class ComposeLayoutInspector(
     connection: Connection,
+    @Suppress("unused")
     private val environment: InspectorEnvironment
 ) : Inspector(connection) {
 
     /** Cache data which allows us to reuse previously queried inspector nodes */
     private class CacheData(
         val rootView: View,
+        val trees: List<CacheTree>
+    ) {
+        /** The cached nodes as a map from node id to InspectorNode */
+        val lookup: Map<Long, InspectorNode>
+            get() = _lookup ?: trees.flatMap { it.nodes }
+                .flatMap { it.flatten() }
+                .associateBy { it.id }
+                .also { _lookup = it }
+
+        private var _lookup: Map<Long, InspectorNode>? = null
+    }
+
+    /** Cache data for a tree of [InspectorNode]s under a [viewParent] */
+    internal class CacheTree(
         val viewParent: View,
         val nodes: List<InspectorNode>,
         val viewsToSkip: List<Long>
-    ) {
-        /** The cached nodes as a map from node id to InspectorNode */
-        val lookup = nodes.flatMap { it.flatten() }.associateBy { it.id }
-    }
+    )
 
     private val layoutInspectorTree = LayoutInspectorTree()
 
@@ -142,21 +153,13 @@
         val windowPos = IntOffset(location[0], location[1])
 
         val stringTable = StringTable()
-        val nodes = data?.nodes ?: emptyList()
-        val composeNodes = nodes.toComposableNodes(stringTable, windowPos)
+        val trees = data?.trees ?: emptyList()
+        val roots = trees.map { it.toComposableRoot(stringTable, windowPos) }
 
         callback.reply {
             getComposablesResponse = GetComposablesResponse.newBuilder().apply {
                 addAllStrings(stringTable.toStringEntries())
-                addRoots(
-                    ComposableRoot.newBuilder().apply {
-                        if (data != null) {
-                            viewId = data.viewParent.uniqueDrawingId
-                            addAllNodes(composeNodes)
-                            addAllViewsToSkip(data.viewsToSkip)
-                        }
-                    }
-                )
+                addAllRoots(roots)
             }.build()
         }
     }
@@ -284,15 +287,20 @@
 
         val data = ThreadUtils.runOnMainThread {
             layoutInspectorTree.resetAccumulativeState()
-            val data = getAndroidComposeViews(rootViewId, skipSystemComposables, generation).map {
-                CacheData(it.rootView, it.viewParent, it.createNodes(), it.viewsToSkip)
+            val composeViews = getAndroidComposeViews(rootViewId, skipSystemComposables, generation)
+            val composeViewsByRoot = composeViews.groupBy { it.rootView.uniqueDrawingId }
+            val data = composeViewsByRoot.mapValues { (_, composeViews) ->
+                CacheData(
+                    composeViews.first().rootView,
+                    composeViews.map { CacheTree(it.viewParent, it.createNodes(), it.viewsToSkip) }
+                )
             }
             layoutInspectorTree.resetAccumulativeState()
             data
         }.get()
 
         cachedNodes.clear()
-        data.associateByTo(cachedNodes) { it.rootView.uniqueDrawingId }
+        cachedNodes.putAll(data)
         cachedGeneration = generation
         cachedSystemComposablesSkipped = skipSystemComposables
         return cachedNodes[rootViewId]
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt
index fa17f3c..3526f27 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt
@@ -18,6 +18,7 @@
 
 import android.view.inspector.WindowInspector
 import androidx.annotation.VisibleForTesting
+import androidx.compose.ui.inspection.ComposeLayoutInspector.CacheTree
 import androidx.compose.ui.inspection.LambdaLocation
 import androidx.compose.ui.inspection.inspector.InspectorNode
 import androidx.compose.ui.inspection.inspector.NodeParameter
@@ -28,6 +29,7 @@
 import androidx.compose.ui.unit.IntOffset
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.Bounds
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.ComposableNode
+import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.ComposableRoot
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.LambdaValue
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.Parameter
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.ParameterReference
@@ -233,12 +235,14 @@
     }.build()
 }
 
-fun Iterable<InspectorNode>.toComposableNodes(
+internal fun CacheTree.toComposableRoot(
     stringTable: StringTable,
     windowPos: IntOffset
-): List<ComposableNode> {
-    return this.map { it.toComposableNode(stringTable, windowPos) }
-}
+): ComposableRoot = ComposableRoot.newBuilder().also { root ->
+    root.viewId = viewParent.uniqueDrawingId
+    root.addAllNodes(nodes.map { it.toComposableNode(stringTable, windowPos) })
+    root.addAllViewsToSkip(viewsToSkip)
+}.build()
 
 fun Iterable<NodeParameter>.convertAll(stringTable: StringTable): List<Parameter> {
     return this.map { it.convert(stringTable) }
diff --git a/compose/ui/ui-test-junit4/OWNERS b/compose/ui/ui-test-junit4/OWNERS
index 305021a..b2c0d56 100644
--- a/compose/ui/ui-test-junit4/OWNERS
+++ b/compose/ui/ui-test-junit4/OWNERS
@@ -1,2 +1 @@
-pavlis@google.com
 jellefresen@google.com
diff --git a/compose/ui/ui-test-junit4/api/1.1.0-beta04.txt b/compose/ui/ui-test-junit4/api/1.1.0-beta04.txt
index a71c843..56b11a6 100644
--- a/compose/ui/ui-test-junit4/api/1.1.0-beta04.txt
+++ b/compose/ui/ui-test-junit4/api/1.1.0-beta04.txt
@@ -76,7 +76,7 @@
 
 package androidx.compose.ui.test.junit4.android {
 
-  public final class ComposeNotIdleException extends java.lang.Throwable {
+  public final class ComposeNotIdleException extends java.lang.Exception {
     ctor public ComposeNotIdleException(String? message, Throwable? cause);
   }
 
diff --git a/compose/ui/ui-test-junit4/api/current.ignore b/compose/ui/ui-test-junit4/api/current.ignore
index 6e4d61f..a21b2a6 100644
--- a/compose/ui/ui-test-junit4/api/current.ignore
+++ b/compose/ui/ui-test-junit4/api/current.ignore
@@ -1,7 +1,3 @@
 // Baseline format: 1.0
-RemovedClass: androidx.compose.ui.test.junit4.android.ComposeIdlingResource_androidKt:
-    Removed class androidx.compose.ui.test.junit4.android.ComposeIdlingResource_androidKt
-RemovedClass: androidx.compose.ui.test.junit4.android.ComposeRootRegistry_androidKt:
-    Removed class androidx.compose.ui.test.junit4.android.ComposeRootRegistry_androidKt
-RemovedClass: androidx.compose.ui.test.junit4.android.EspressoLink_androidKt:
-    Removed class androidx.compose.ui.test.junit4.android.EspressoLink_androidKt
+ChangedSuperclass: androidx.compose.ui.test.junit4.android.ComposeNotIdleException:
+    Class androidx.compose.ui.test.junit4.android.ComposeNotIdleException superclass changed from java.lang.Throwable to java.lang.Exception
diff --git a/compose/ui/ui-test-junit4/api/current.txt b/compose/ui/ui-test-junit4/api/current.txt
index a71c843..56b11a6 100644
--- a/compose/ui/ui-test-junit4/api/current.txt
+++ b/compose/ui/ui-test-junit4/api/current.txt
@@ -76,7 +76,7 @@
 
 package androidx.compose.ui.test.junit4.android {
 
-  public final class ComposeNotIdleException extends java.lang.Throwable {
+  public final class ComposeNotIdleException extends java.lang.Exception {
     ctor public ComposeNotIdleException(String? message, Throwable? cause);
   }
 
diff --git a/compose/ui/ui-test-junit4/api/public_plus_experimental_1.1.0-beta04.txt b/compose/ui/ui-test-junit4/api/public_plus_experimental_1.1.0-beta04.txt
index a71c843..56b11a6 100644
--- a/compose/ui/ui-test-junit4/api/public_plus_experimental_1.1.0-beta04.txt
+++ b/compose/ui/ui-test-junit4/api/public_plus_experimental_1.1.0-beta04.txt
@@ -76,7 +76,7 @@
 
 package androidx.compose.ui.test.junit4.android {
 
-  public final class ComposeNotIdleException extends java.lang.Throwable {
+  public final class ComposeNotIdleException extends java.lang.Exception {
     ctor public ComposeNotIdleException(String? message, Throwable? cause);
   }
 
diff --git a/compose/ui/ui-test-junit4/api/public_plus_experimental_current.txt b/compose/ui/ui-test-junit4/api/public_plus_experimental_current.txt
index a71c843..56b11a6 100644
--- a/compose/ui/ui-test-junit4/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-test-junit4/api/public_plus_experimental_current.txt
@@ -76,7 +76,7 @@
 
 package androidx.compose.ui.test.junit4.android {
 
-  public final class ComposeNotIdleException extends java.lang.Throwable {
+  public final class ComposeNotIdleException extends java.lang.Exception {
     ctor public ComposeNotIdleException(String? message, Throwable? cause);
   }
 
diff --git a/compose/ui/ui-test-junit4/api/restricted_1.1.0-beta04.txt b/compose/ui/ui-test-junit4/api/restricted_1.1.0-beta04.txt
index a71c843..56b11a6 100644
--- a/compose/ui/ui-test-junit4/api/restricted_1.1.0-beta04.txt
+++ b/compose/ui/ui-test-junit4/api/restricted_1.1.0-beta04.txt
@@ -76,7 +76,7 @@
 
 package androidx.compose.ui.test.junit4.android {
 
-  public final class ComposeNotIdleException extends java.lang.Throwable {
+  public final class ComposeNotIdleException extends java.lang.Exception {
     ctor public ComposeNotIdleException(String? message, Throwable? cause);
   }
 
diff --git a/compose/ui/ui-test-junit4/api/restricted_current.ignore b/compose/ui/ui-test-junit4/api/restricted_current.ignore
index 6e4d61f..a21b2a6 100644
--- a/compose/ui/ui-test-junit4/api/restricted_current.ignore
+++ b/compose/ui/ui-test-junit4/api/restricted_current.ignore
@@ -1,7 +1,3 @@
 // Baseline format: 1.0
-RemovedClass: androidx.compose.ui.test.junit4.android.ComposeIdlingResource_androidKt:
-    Removed class androidx.compose.ui.test.junit4.android.ComposeIdlingResource_androidKt
-RemovedClass: androidx.compose.ui.test.junit4.android.ComposeRootRegistry_androidKt:
-    Removed class androidx.compose.ui.test.junit4.android.ComposeRootRegistry_androidKt
-RemovedClass: androidx.compose.ui.test.junit4.android.EspressoLink_androidKt:
-    Removed class androidx.compose.ui.test.junit4.android.EspressoLink_androidKt
+ChangedSuperclass: androidx.compose.ui.test.junit4.android.ComposeNotIdleException:
+    Class androidx.compose.ui.test.junit4.android.ComposeNotIdleException superclass changed from java.lang.Throwable to java.lang.Exception
diff --git a/compose/ui/ui-test-junit4/api/restricted_current.txt b/compose/ui/ui-test-junit4/api/restricted_current.txt
index a71c843..56b11a6 100644
--- a/compose/ui/ui-test-junit4/api/restricted_current.txt
+++ b/compose/ui/ui-test-junit4/api/restricted_current.txt
@@ -76,7 +76,7 @@
 
 package androidx.compose.ui.test.junit4.android {
 
-  public final class ComposeNotIdleException extends java.lang.Throwable {
+  public final class ComposeNotIdleException extends java.lang.Exception {
     ctor public ComposeNotIdleException(String? message, Throwable? cause);
   }
 
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeNotIdleException.android.kt b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeNotIdleException.android.kt
index bd68df1..815c8d6 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeNotIdleException.android.kt
+++ b/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeNotIdleException.android.kt
@@ -19,4 +19,4 @@
 /**
  * Thrown in cases where Compose can't get idle in Espresso's defined time limit.
  */
-class ComposeNotIdleException(message: String?, cause: Throwable?) : Throwable(message, cause)
+class ComposeNotIdleException(message: String?, cause: Throwable?) : Exception(message, cause)
diff --git a/compose/ui/ui-test-manifest/OWNERS b/compose/ui/ui-test-manifest/OWNERS
index 42abc4e..b2c0d56 100644
--- a/compose/ui/ui-test-manifest/OWNERS
+++ b/compose/ui/ui-test-manifest/OWNERS
@@ -1,2 +1 @@
 jellefresen@google.com
-pavlis@google.com
diff --git a/compose/ui/ui-test-manifest/integration-tests/testapp/OWNERS b/compose/ui/ui-test-manifest/integration-tests/testapp/OWNERS
index 42abc4e..b2c0d56 100644
--- a/compose/ui/ui-test-manifest/integration-tests/testapp/OWNERS
+++ b/compose/ui/ui-test-manifest/integration-tests/testapp/OWNERS
@@ -1,2 +1 @@
 jellefresen@google.com
-pavlis@google.com
diff --git a/compose/ui/ui-test/OWNERS b/compose/ui/ui-test/OWNERS
index 305021a..b2c0d56 100644
--- a/compose/ui/ui-test/OWNERS
+++ b/compose/ui/ui-test/OWNERS
@@ -1,2 +1 @@
-pavlis@google.com
 jellefresen@google.com
diff --git a/compose/ui/ui-text/OWNERS b/compose/ui/ui-text/OWNERS
index f00bb31..9a0b796 100644
--- a/compose/ui/ui-text/OWNERS
+++ b/compose/ui/ui-text/OWNERS
@@ -1,6 +1,5 @@
 include /TEXT_OWNERS
 
-pavlis@google.com
 adamp@google.com
 mount@google.com
 popam@google.com
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TempListUtils.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TempListUtils.kt
index 79b7c59..b86d941 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TempListUtils.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TempListUtils.kt
@@ -24,6 +24,10 @@
 
 /**
  * Returns a list containing only elements matching the given [predicate].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
@@ -40,6 +44,10 @@
  * having distinct keys returned by the given [selector] function.
  *
  * The elements in the resulting list are in the same order as they were in the source collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
@@ -56,6 +64,10 @@
 /**
  * Returns the first element yielding the largest value of the given function or `null` if there
  * are no elements.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R : Comparable<R>> List<T>.fastMinByOrNull(selector: (T) -> R): T? {
@@ -80,7 +92,12 @@
  *
  * Returns the specified [initial] value if the collection is empty.
  *
- * @param [operation] function that takes current accumulator value and an element, and calculates the next accumulator value.
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
+ *
+ * @param [operation] function that takes current accumulator value and an element, and calculates
+ * the next accumulator value.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R> List<T>.fastFold(initial: R, operation: (acc: R, T) -> R): R {
@@ -95,6 +112,10 @@
 /**
  * Returns a single list of all elements yielded from results of [transform] function being invoked
  * on each element of original collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R> List<T>.fastFlatMap(transform: (T) -> Iterable<R>): List<R> {
@@ -109,6 +130,10 @@
 
 /**
  * Returns a list containing all elements not matching the given [predicate].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
@@ -122,6 +147,10 @@
 
 /**
  * Returns a list containing the first elements satisfying the given [predicate].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T> List<T>.fastTakeWhile(predicate: (T) -> Boolean): List<T> {
@@ -139,6 +168,10 @@
 /**
  * Returns a list containing all elements except first [n] elements.
  *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
+ *
  * @throws IllegalArgumentException if [n] is negative.
  */
 internal fun <T> List<T>.fastDrop(n: Int): List<T> {
@@ -167,6 +200,10 @@
  * If the collection could be huge, you can specify a non-negative value of [limit], in which case
  * only the first [limit] elements will be appended, followed by the [truncated] string (which
  * defaults to "...").
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 internal fun <T> List<T>.fastJoinToString(
     separator: CharSequence = ", ",
@@ -187,6 +224,10 @@
  * If the collection could be huge, you can specify a non-negative value of [limit], in which
  * case only the first [limit] elements will be appended, followed by the [truncated] string
  * (which defaults to "...").
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 private fun <T, A : Appendable> List<T>.fastJoinTo(
     buffer: A,
diff --git a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
index bf2f53d..bfa9798 100644
--- a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
+++ b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
@@ -22,6 +22,10 @@
 /**
  * Iterates through a [List] using the index and calls [action] for each item.
  * This does not allocate an iterator like [Iterable.forEach].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 inline fun <T> List<T>.fastForEach(action: (T) -> Unit) {
@@ -35,6 +39,10 @@
 /**
  * Iterates through a [List] using the index and calls [action] for each item.
  * This does not allocate an iterator like [Iterable.forEachIndexed].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 inline fun <T> List<T>.fastForEachIndexed(action: (Int, T) -> Unit) {
@@ -47,6 +55,10 @@
 
 /**
  * Returns `true` if all elements match the given [predicate].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 inline fun <T> List<T>.fastAll(predicate: (T) -> Boolean): Boolean {
@@ -57,6 +69,10 @@
 
 /**
  * Returns `true` if at least one element matches the given [predicate].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 inline fun <T> List<T>.fastAny(predicate: (T) -> Boolean): Boolean {
@@ -67,6 +83,10 @@
 
 /**
  * Returns the first value that [predicate] returns `true` for or `null` if nothing matches.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 inline fun <T> List<T>.fastFirstOrNull(predicate: (T) -> Boolean): T? {
@@ -78,6 +98,10 @@
 /**
  * Returns the sum of all values produced by [selector] function applied to each element in the
  * list.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 inline fun <T> List<T>.fastSumBy(selector: (T) -> Int): Int {
@@ -92,6 +116,10 @@
 /**
  * Returns a list containing the results of applying the given [transform] function
  * to each element in the original collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 inline fun <T, R> List<T>.fastMap(transform: (T) -> R): List<R> {
@@ -107,6 +135,10 @@
 /**
  * Returns the first element yielding the largest value of the given function or `null` if there
  * are no elements.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 inline fun <T, R : Comparable<R>> List<T>.fastMaxBy(selector: (T) -> R): T? {
@@ -128,6 +160,10 @@
 /**
  * Applies the given [transform] function to each element of the original collection
  * and appends the results to the given [destination].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 inline fun <T, R, C : MutableCollection<in R>> List<T>.fastMapTo(
diff --git a/compose/ui/ui/api/public_plus_experimental_1.1.0-beta04.txt b/compose/ui/ui/api/public_plus_experimental_1.1.0-beta04.txt
index b293eee..eced941 100644
--- a/compose/ui/ui/api/public_plus_experimental_1.1.0-beta04.txt
+++ b/compose/ui/ui/api/public_plus_experimental_1.1.0-beta04.txt
@@ -1607,12 +1607,14 @@
     method public int getMove();
     method public int getPress();
     method public int getRelease();
+    method @androidx.compose.ui.ExperimentalComposeUiApi public int getScroll();
     method public int getUnknown();
     property public final int Enter;
     property public final int Exit;
     property public final int Move;
     property public final int Press;
     property public final int Release;
+    property @androidx.compose.ui.ExperimentalComposeUiApi public final int Scroll;
     property public final int Unknown;
   }
 
@@ -1670,7 +1672,7 @@
 
   @androidx.compose.runtime.Immutable public final class PointerInputChange {
     ctor public PointerInputChange(long id, long uptimeMillis, long position, boolean pressed, long previousUptimeMillis, long previousPosition, boolean previousPressed, androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type);
-    ctor @androidx.compose.ui.ExperimentalComposeUiApi public PointerInputChange(long id, long uptimeMillis, long position, boolean pressed, long previousUptimeMillis, long previousPosition, boolean previousPressed, androidx.compose.ui.input.pointer.ConsumedData consumed, int type, java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical);
+    method @androidx.compose.ui.ExperimentalComposeUiApi public void consume();
     method public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type);
     method @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type, java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical);
     method public androidx.compose.ui.input.pointer.ConsumedData getConsumed();
@@ -1681,16 +1683,20 @@
     method public long getPreviousPosition();
     method public boolean getPreviousPressed();
     method public long getPreviousUptimeMillis();
+    method @androidx.compose.ui.ExperimentalComposeUiApi public long getScrollDelta();
     method public int getType();
     method public long getUptimeMillis();
+    method @androidx.compose.ui.ExperimentalComposeUiApi public boolean isConsumed();
     property public final androidx.compose.ui.input.pointer.ConsumedData consumed;
     property @androidx.compose.ui.ExperimentalComposeUiApi public final java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical;
     property public final long id;
+    property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean isConsumed;
     property public final long position;
     property public final boolean pressed;
     property public final long previousPosition;
     property public final boolean previousPressed;
     property public final long previousUptimeMillis;
+    property @androidx.compose.ui.ExperimentalComposeUiApi public final long scrollDelta;
     property public final int type;
     property public final long uptimeMillis;
   }
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index b293eee..eced941 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -1607,12 +1607,14 @@
     method public int getMove();
     method public int getPress();
     method public int getRelease();
+    method @androidx.compose.ui.ExperimentalComposeUiApi public int getScroll();
     method public int getUnknown();
     property public final int Enter;
     property public final int Exit;
     property public final int Move;
     property public final int Press;
     property public final int Release;
+    property @androidx.compose.ui.ExperimentalComposeUiApi public final int Scroll;
     property public final int Unknown;
   }
 
@@ -1670,7 +1672,7 @@
 
   @androidx.compose.runtime.Immutable public final class PointerInputChange {
     ctor public PointerInputChange(long id, long uptimeMillis, long position, boolean pressed, long previousUptimeMillis, long previousPosition, boolean previousPressed, androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type);
-    ctor @androidx.compose.ui.ExperimentalComposeUiApi public PointerInputChange(long id, long uptimeMillis, long position, boolean pressed, long previousUptimeMillis, long previousPosition, boolean previousPressed, androidx.compose.ui.input.pointer.ConsumedData consumed, int type, java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical);
+    method @androidx.compose.ui.ExperimentalComposeUiApi public void consume();
     method public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type);
     method @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type, java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical);
     method public androidx.compose.ui.input.pointer.ConsumedData getConsumed();
@@ -1681,16 +1683,20 @@
     method public long getPreviousPosition();
     method public boolean getPreviousPressed();
     method public long getPreviousUptimeMillis();
+    method @androidx.compose.ui.ExperimentalComposeUiApi public long getScrollDelta();
     method public int getType();
     method public long getUptimeMillis();
+    method @androidx.compose.ui.ExperimentalComposeUiApi public boolean isConsumed();
     property public final androidx.compose.ui.input.pointer.ConsumedData consumed;
     property @androidx.compose.ui.ExperimentalComposeUiApi public final java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical;
     property public final long id;
+    property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean isConsumed;
     property public final long position;
     property public final boolean pressed;
     property public final long previousPosition;
     property public final boolean previousPressed;
     property public final long previousUptimeMillis;
+    property @androidx.compose.ui.ExperimentalComposeUiApi public final long scrollDelta;
     property public final int type;
     property public final long uptimeMillis;
   }
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/LayoutNodeModifierBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/LayoutNodeModifierBenchmark.kt
index e93fc61..f462daa 100644
--- a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/LayoutNodeModifierBenchmark.kt
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/LayoutNodeModifierBenchmark.kt
@@ -121,6 +121,41 @@
         }
     }
 
+    @Test
+    fun setDrawModifiersToSameValue() {
+        modifiers = mutableListOf<Modifier>().apply {
+            repeat(numberOfModifiers) {
+                this += Modifier.drawBehind { }
+            }
+        }
+        combinedModifier = modifiers.fold<Modifier, Modifier>(Modifier) { acc, modifier ->
+            acc.then(modifier)
+        }
+
+        val altModifier = mutableListOf<Modifier>().apply {
+            repeat(numberOfModifiers) {
+                this += Modifier.drawBehind { }
+            }
+        }.fold<Modifier, Modifier>(Modifier) { acc, modifier ->
+            acc.then(modifier)
+        }
+
+        rule.activityTestRule.runOnUiThread {
+            rule.activityTestRule.activity.setContent {
+                TestModifierUpdaterLayout {
+                    testModifierUpdater = it
+                }
+            }
+        }
+
+        rule.activityTestRule.runOnUiThread {
+            rule.benchmarkRule.measureRepeated {
+                testModifierUpdater.updateModifier(combinedModifier)
+                testModifierUpdater.updateModifier(altModifier)
+            }
+        }
+    }
+
     class SimpleAndroidBenchmarkRule() : TestRule {
         @Suppress("DEPRECATION")
         val activityTestRule =
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/EventTypesDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/EventTypesDemo.kt
index 36b1565..29478d5 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/EventTypesDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/EventTypesDemo.kt
@@ -22,7 +22,6 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.material.Text
@@ -30,8 +29,10 @@
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventType
@@ -50,10 +51,10 @@
 }
 
 @Composable
-private fun DrawEvents(events: List<PointerEventType>, counts: List<Int>) {
+@OptIn(ExperimentalComposeUiApi::class)
+private fun DrawEvents(events: List<Pair<PointerEventType, Any>>) {
     for (i in events.lastIndex downTo 0) {
-        val type = events[i]
-        val count = counts[i]
+        val (type, value) = events[i]
 
         val color = when (type) {
             PointerEventType.Press -> Color.Red
@@ -61,9 +62,10 @@
             PointerEventType.Release -> Color.Yellow
             PointerEventType.Enter -> Color.Green
             PointerEventType.Exit -> Color.Blue
-            else -> Color(0xFF800080) // Purple
+            PointerEventType.Scroll -> Color(0xFF800080) // Purple
+            else -> Color.Black
         }
-        TextItem("$type $count", color)
+        TextItem("$type $value", color)
     }
 }
 
@@ -72,59 +74,62 @@
  */
 @Composable
 fun EventTypesDemo() {
-    val innerPointerEventTypes = remember { mutableStateListOf<PointerEventType>() }
-    val innerPointerEventCounts = remember { mutableStateListOf<Int>() }
-    val outerPointerEventTypes = remember { mutableStateListOf<PointerEventType>() }
-    val outerPointerEventCounts = remember { mutableStateListOf<Int>() }
+    val innerPointerEvents = remember { mutableStateListOf<Pair<PointerEventType, Any>>() }
+    val outerPointerEvents = remember { mutableStateListOf<Pair<PointerEventType, Any>>() }
     Box(
         Modifier.pointerInput(Unit) {
             awaitPointerEventScope {
                 while (true) {
                     val event = awaitPointerEvent()
                     event.changes.forEach { it.consumeAllChanges() }
-                    addEvent(event, outerPointerEventTypes, outerPointerEventCounts)
+                    addEvent(event, outerPointerEvents)
                 }
             }
         }
     ) {
         Column {
-            DrawEvents(outerPointerEventTypes, outerPointerEventCounts)
+            DrawEvents(outerPointerEvents)
         }
         Column(
-            Modifier
-                .align(Alignment.CenterEnd)
-                .requiredSize(200.dp)
+            Modifier.size(200.dp)
                 .border(2.dp, Color.Black)
+                .align(Alignment.CenterEnd)
                 .clipToBounds()
                 .pointerInput(Unit) {
                     awaitPointerEventScope {
                         while (true) {
                             val event = awaitPointerEvent()
-                            addEvent(event, innerPointerEventTypes, innerPointerEventCounts)
+                            addEvent(event, innerPointerEvents)
                         }
                     }
                 }
         ) {
-            DrawEvents(innerPointerEventTypes, innerPointerEventCounts)
+            DrawEvents(innerPointerEvents)
         }
     }
 }
 
+@OptIn(ExperimentalComposeUiApi::class)
 private fun addEvent(
     event: PointerEvent,
-    events: MutableList<PointerEventType>,
-    counts: MutableList<Int>
+    events: MutableList<Pair<PointerEventType, Any>>,
 ) {
     event.changes.forEach { it.consumeAllChanges() }
-    if (events.lastOrNull() == event.type) {
-        counts[counts.lastIndex]++
+    val scrollTotal = event.changes.foldRight(Offset.Zero) { c, acc -> acc + c.scrollDelta }
+    if (events.lastOrNull()?.first == event.type) {
+        val (type, value) = events.last()
+        if (type == PointerEventType.Scroll) {
+            events[events.lastIndex] = type to ((value as Offset) + scrollTotal)
+        } else {
+            events[events.lastIndex] = type to ((value as Int) + 1)
+        }
+    } else if (event.type == PointerEventType.Scroll) {
+        events += event.type to scrollTotal
     } else {
-        events += event.type
-        counts += 1
+        events += event.type to 1
     }
 
     while (events.size > 100) {
         events.removeAt(0)
-        counts.removeAt(0)
     }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index c1b7f74..2c6747b 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -38,6 +38,7 @@
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Stable
@@ -49,6 +50,7 @@
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.testutils.assertPixels
 import androidx.compose.ui.draw.DrawModifier
+import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.Offset
@@ -3575,6 +3577,76 @@
         assertTrue(composeView.isTransitionGroup)
     }
 
+    @Test
+    fun drawnInCorrectLayer() {
+        var innerDrawLatch = CountDownLatch(1)
+        var outerDrawLatch = CountDownLatch(1)
+        var outerColor by mutableStateOf(Color.Blue)
+        var innerColor by mutableStateOf(Color.White)
+        activityTestRule.runOnUiThread {
+            activity.setContent {
+                with(LocalDensity.current) {
+                    Box(Modifier.size(30.toDp())
+                        .drawBehind {
+                            drawRect(outerColor)
+                            outerDrawLatch.countDown()
+                        }
+                        .drawLatchModifier()
+                        .padding(10.toDp())
+                        .clipToBounds()
+                        .drawBehind {
+                            // clipped by the layer
+                            drawRect(innerColor, Offset(-10f, -10f), Size(30f, 30f))
+                            innerDrawLatch.countDown()
+                        }
+                        .drawLatchModifier()
+                        .size(10.toDp())
+                    )
+                }
+            }
+        }
+        assertTrue(innerDrawLatch.await(1, TimeUnit.SECONDS))
+        assertTrue(outerDrawLatch.await(1, TimeUnit.SECONDS))
+
+        validateSquareColors(
+            outerColor = Color.Blue,
+            innerColor = Color.White,
+            size = 10
+        )
+
+        innerDrawLatch = CountDownLatch(1)
+        outerDrawLatch = CountDownLatch(1)
+        drawLatch = CountDownLatch(1)
+
+        // changing the inner color should only affect the inner layer
+        innerColor = Color.Yellow
+
+        assertTrue(innerDrawLatch.await(1, TimeUnit.SECONDS))
+
+        validateSquareColors(
+            outerColor = Color.Blue,
+            innerColor = Color.Yellow,
+            size = 10
+        )
+
+        assertEquals(1, outerDrawLatch.count)
+        innerDrawLatch = CountDownLatch(1)
+        drawLatch = CountDownLatch(1)
+
+        // changing the outer color should only affect the outer layer
+        outerColor = Color.Red
+
+        assertTrue(outerDrawLatch.await(1, TimeUnit.SECONDS))
+
+        validateSquareColors(
+            outerColor = Color.Red,
+            innerColor = Color.Yellow,
+            size = 10
+        )
+
+        assertEquals(1, innerDrawLatch.count)
+    }
+
     private fun Modifier.layout(onLayout: () -> Unit) = layout { measurable, constraints ->
         val placeable = measurable.measure(constraints)
         layout(placeable.width, placeable.height) {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/Utils.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/Utils.kt
index 31dc186..fb57437 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/Utils.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/gesture/Utils.kt
@@ -17,6 +17,8 @@
 package androidx.compose.ui.gesture
 
 import android.view.MotionEvent
+import android.view.MotionEvent.AXIS_HSCROLL
+import android.view.MotionEvent.AXIS_VSCROLL
 import android.view.View
 
 // We only need this because IR compiler doesn't like converting lambdas to Runnables
@@ -87,8 +89,10 @@
     MotionEvent.PointerProperties().apply { this.id = id }
 
 @Suppress("RemoveRedundantQualifierName")
-internal fun PointerCoords(x: Float, y: Float) =
+internal fun PointerCoords(x: Float, y: Float, scrollX: Float = 0f, scrollY: Float = 0f) =
     MotionEvent.PointerCoords().apply {
         this.x = x
         this.y = y
+        setAxisValue(AXIS_HSCROLL, scrollX)
+        setAxisValue(AXIS_VSCROLL, scrollY)
     }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
index 5f5158f..4db8990 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
@@ -28,6 +28,7 @@
 import android.view.MotionEvent.ACTION_HOVER_MOVE
 import android.view.MotionEvent.ACTION_MOVE
 import android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT
+import android.view.MotionEvent.ACTION_SCROLL
 import android.view.MotionEvent.ACTION_UP
 import android.view.MotionEvent.TOOL_TYPE_FINGER
 import android.view.MotionEvent.TOOL_TYPE_MOUSE
@@ -50,6 +51,7 @@
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.AbsoluteAlignment
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.OpenComposeView
 import androidx.compose.ui.composed
@@ -70,6 +72,7 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -740,10 +743,24 @@
         assertThat(event.type).isEqualTo(expectedHoverType)
     }
 
+    @OptIn(ExperimentalComposeUiApi::class)
+    private fun assertScrollEvent(
+        event: PointerEvent,
+        scrollExpected: Offset
+    ) {
+        assertThat(event.changes).hasSize(1)
+        val change = event.changes[0]
+        assertThat(change.pressed).isFalse()
+        assertThat(event.type).isEqualTo(PointerEventType.Scroll)
+        // we agreed to reverse the delta in android to be in line with other platforms
+        assertThat(change.scrollDelta).isEqualTo(-scrollExpected)
+    }
+
     private fun dispatchMouseEvent(
-        action: Int = ACTION_HOVER_ENTER,
+        action: Int,
         layoutCoordinates: LayoutCoordinates,
-        offset: Offset = Offset.Zero
+        offset: Offset = Offset.Zero,
+        scrollDelta: Offset = Offset.Zero
     ) {
         rule.runOnUiThread {
             val root = layoutCoordinates.findRoot()
@@ -754,13 +771,14 @@
                 1,
                 0,
                 arrayOf(PointerProperties(0).also { it.toolType = MotionEvent.TOOL_TYPE_MOUSE }),
-                arrayOf(PointerCoords(pos.x, pos.y))
+                arrayOf(PointerCoords(pos.x, pos.y, scrollDelta.x, scrollDelta.y))
             )
 
             val androidComposeView = findAndroidComposeView(container) as AndroidComposeView
             when (action) {
                 ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT ->
                     androidComposeView.dispatchHoverEvent(event)
+                ACTION_SCROLL -> androidComposeView.dispatchGenericMotionEvent(event)
                 else -> androidComposeView.dispatchTouchEvent(event)
             }
         }
@@ -981,6 +999,149 @@
     }
 
     @Test
+    fun dispatchScroll() {
+        var layoutCoordinates: LayoutCoordinates? = null
+        val latch = CountDownLatch(1)
+        val events = mutableListOf<PointerEvent>()
+        val scrollDelta = Offset(0.35f, 0.65f)
+        rule.runOnUiThread {
+            container.setContent {
+                Box(
+                    Modifier.fillMaxSize().onGloballyPositioned {
+                        layoutCoordinates = it
+                        latch.countDown()
+                    }.pointerInput(Unit) {
+                        awaitPointerEventScope {
+                            while (true) {
+                                val event = awaitPointerEvent()
+                                event.changes[0].consumeAllChanges()
+                                events += event
+                            }
+                        }
+                    }
+                )
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+        dispatchMouseEvent(ACTION_SCROLL, layoutCoordinates!!, scrollDelta = scrollDelta)
+        rule.runOnUiThread {
+            assertThat(events).hasSize(2) // synthetic enter and scroll
+            assertHoverEvent(events[0], isEnter = true)
+            assertScrollEvent(events[1], scrollExpected = scrollDelta)
+        }
+    }
+
+    @Test
+    fun dispatchScroll_whenButtonPressed() {
+        var layoutCoordinates: LayoutCoordinates? = null
+        val latch = CountDownLatch(1)
+        val events = mutableListOf<PointerEvent>()
+        val scrollDelta = Offset(0.35f, 0.65f)
+        rule.runOnUiThread {
+            container.setContent {
+                Box(
+                    Modifier.fillMaxSize().onGloballyPositioned {
+                        layoutCoordinates = it
+                        latch.countDown()
+                    }.pointerInput(Unit) {
+                        awaitPointerEventScope {
+                            while (true) {
+                                val event = awaitPointerEvent()
+                                event.changes[0].consumeAllChanges()
+                                events += event
+                            }
+                        }
+                    }
+                )
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+        // press the button first before scroll
+        dispatchMouseEvent(ACTION_DOWN, layoutCoordinates!!)
+        dispatchMouseEvent(ACTION_SCROLL, layoutCoordinates!!, scrollDelta = scrollDelta)
+        rule.runOnUiThread {
+            assertThat(events).hasSize(3) // synthetic enter, button down, scroll
+            assertHoverEvent(events[0], isEnter = true)
+            assert(events[1].changes.fastAll { it.changedToDownIgnoreConsumed() })
+            assertScrollEvent(events[2], scrollExpected = scrollDelta)
+        }
+    }
+
+    @Test
+    fun dispatchScroll_batch() {
+        var layoutCoordinates: LayoutCoordinates? = null
+        val latch = CountDownLatch(1)
+        val events = mutableListOf<PointerEvent>()
+        val scrollDelta1 = Offset(0.32f, -0.75f)
+        val scrollDelta2 = Offset(0.14f, 0.35f)
+        val scrollDelta3 = Offset(-0.30f, -0.12f)
+        val scrollDelta4 = Offset(-0.05f, 0.68f)
+        rule.runOnUiThread {
+            container.setContent {
+                Box(
+                    Modifier.fillMaxSize().onGloballyPositioned {
+                        layoutCoordinates = it
+                        latch.countDown()
+                    }.pointerInput(Unit) {
+                        awaitPointerEventScope {
+                            while (true) {
+                                val event = awaitPointerEvent()
+                                event.changes[0].consumeAllChanges()
+                                events += event
+                            }
+                        }
+                    }
+                )
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+        listOf(scrollDelta1, scrollDelta2, scrollDelta3, scrollDelta4).fastForEach {
+            dispatchMouseEvent(ACTION_SCROLL, layoutCoordinates!!, scrollDelta = it)
+        }
+        rule.runOnUiThread {
+            assertThat(events).hasSize(5) // 4 + synthetic enter
+            assertHoverEvent(events[0], isEnter = true)
+            assertScrollEvent(events[1], scrollExpected = scrollDelta1)
+            assertScrollEvent(events[2], scrollExpected = scrollDelta2)
+            assertScrollEvent(events[3], scrollExpected = scrollDelta3)
+            assertScrollEvent(events[4], scrollExpected = scrollDelta4)
+        }
+    }
+
+    @Test
+    fun mouseScroll_ignoredAsDownEvent() {
+        var layoutCoordinates: LayoutCoordinates? = null
+        val latch = CountDownLatch(1)
+        val events = mutableListOf<PointerEvent>()
+        val scrollDelta = Offset(0.35f, 0.65f)
+        rule.runOnUiThread {
+            container.setContent {
+                Box(
+                    Modifier.fillMaxSize().onGloballyPositioned {
+                        layoutCoordinates = it
+                        latch.countDown()
+                    }.pointerInput(Unit) {
+                        awaitPointerEventScope {
+                            while (true) {
+                                val event = awaitPointerEvent()
+                                event.changes[0].consumeAllChanges()
+                                events += event
+                            }
+                        }
+                    }
+                )
+            }
+        }
+        assertTrue(latch.await(1, TimeUnit.SECONDS))
+        dispatchMouseEvent(ACTION_SCROLL, layoutCoordinates!!, scrollDelta = scrollDelta)
+        rule.runOnUiThread {
+            assertThat(events).hasSize(2) // hover enter + scroll
+            assertThat(events[1].changes).isNotEmpty()
+            assertThat(events[1].changes[0].changedToDown()).isFalse()
+        }
+    }
+
+    @Test
     fun hoverEnterPressExitEnterExitRelease() {
         var outerCoordinates: LayoutCoordinates? = null
         var innerCoordinates: LayoutCoordinates? = null
@@ -1404,7 +1565,8 @@
 
         // Hit the bottom box, but clipped
         dispatchMouseEvent(ACTION_HOVER_MOVE, coords)
-        dispatchMouseEvent(ACTION_HOVER_MOVE, coords,
+        dispatchMouseEvent(
+            ACTION_HOVER_MOVE, coords,
             Offset(0f, (coords.size.height / 2 - 1).toFloat())
         )
 
@@ -1413,17 +1575,20 @@
         }
 
         // Now hit the box in the unclipped region
-        dispatchMouseEvent(ACTION_HOVER_MOVE, coords,
+        dispatchMouseEvent(
+            ACTION_HOVER_MOVE, coords,
             Offset(0f, (coords.size.height / 2 + 1).toFloat())
         )
 
         // Now hit the bottom of the clipped region
-        dispatchMouseEvent(ACTION_HOVER_MOVE, coords,
+        dispatchMouseEvent(
+            ACTION_HOVER_MOVE, coords,
             Offset(0f, (coords.size.height - 1).toFloat())
         )
 
         // Now leave
-        dispatchMouseEvent(ACTION_HOVER_MOVE, coords,
+        dispatchMouseEvent(
+            ACTION_HOVER_MOVE, coords,
             Offset(0f, coords.size.height.toFloat() + 1f)
         )
 
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/NodesRemeasuredOnceTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/NodesRemeasuredOnceTest.kt
index 51ee357..28bb807 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/NodesRemeasuredOnceTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/NodesRemeasuredOnceTest.kt
@@ -119,6 +119,34 @@
             assertThat(remeasurements).isEqualTo(2)
         }
     }
+
+    @Test
+    fun remeasuringChildWithExtraLayer_notPlacedChild() {
+        val height = mutableStateOf(10)
+        var remeasurements = 0
+
+        rule.setContent {
+            WrapChild(onMeasured = { actualHeight ->
+                assertThat(actualHeight).isEqualTo(height.value)
+                remeasurements++
+            }) {
+                NotPlaceChild(height) {
+                    WrapChild {
+                        Child(height)
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(remeasurements).isEqualTo(1)
+            height.value = 20
+        }
+
+        rule.runOnIdle {
+            assertThat(remeasurements).isEqualTo(2)
+        }
+    }
 }
 
 @Composable
@@ -134,6 +162,16 @@
 }
 
 @Composable
+private fun NotPlaceChild(height: State<Int>, content: @Composable () -> Unit) {
+    Layout(content = content) { measurables, constraints ->
+        layout(constraints.maxWidth, height.value) {
+            measurables.first()
+                .measure(constraints.copy(minHeight = 0, maxHeight = Constraints.Infinity))
+        }
+    }
+}
+
+@Composable
 private fun Child(height: State<Int>) {
     Layout { _, constraints ->
         layout(constraints.maxWidth, height.value) {}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
index 6193daf..84cf92a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
@@ -27,6 +27,7 @@
 import android.view.MotionEvent.ACTION_HOVER_MOVE
 import android.view.MotionEvent.ACTION_POINTER_DOWN
 import android.view.MotionEvent.ACTION_POINTER_UP
+import android.view.MotionEvent.ACTION_SCROLL
 import android.view.MotionEvent.ACTION_UP
 import android.view.MotionEvent.TOOL_TYPE_ERASER
 import android.view.MotionEvent.TOOL_TYPE_FINGER
@@ -95,6 +96,7 @@
 
         val isHover = action == ACTION_HOVER_EXIT || action == ACTION_HOVER_MOVE ||
             action == ACTION_HOVER_ENTER
+        val isScroll = action == ACTION_SCROLL
 
         if (isHover) {
             val hoverId = motionEvent.getPointerId(motionEvent.actionIndex)
@@ -117,7 +119,11 @@
                     positionCalculator,
                     motionEvent,
                     i,
-                    !isHover && i != upIndex
+                    // "pressed" means:
+                    // 1. we're not hovered
+                    // 2. we didn't get UP event for a pointer
+                    // 3. button on the mouse is pressed BUT it's not a "scroll" simulated button
+                    !isHover && i != upIndex && (!isScroll || motionEvent.buttonState != 0)
                 )
             )
         }
@@ -277,6 +283,17 @@
                 }
             }
         }
+        val scrollDelta = if (motionEvent.actionMasked == ACTION_SCROLL) {
+            val x = motionEvent.getAxisValue(MotionEvent.AXIS_HSCROLL)
+            val y = motionEvent.getAxisValue(MotionEvent.AXIS_VSCROLL)
+            // NOTE: we revert the scroll offset because android is special compared to other
+            // platforms and send the reversed sign for up and down mouse wheel scroll. In order to
+            // support better x-platform mouse scroll, we revert to be in line with desktop
+            // platforms.
+            Offset(x, y) * -1f
+        } else {
+            Offset.Zero
+        }
 
         val issuesEnterExit = canHover.get(motionEvent.getPointerId(index), false)
         return PointerInputEventData(
@@ -287,7 +304,8 @@
             pressed,
             toolType,
             issuesEnterExit,
-            historical
+            historical,
+            scrollDelta
         )
     }
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.android.kt
index fde4a0a..82ac8cb 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.android.kt
@@ -18,6 +18,8 @@
 
 import android.view.KeyEvent
 import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_SCROLL
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
 
@@ -49,6 +51,7 @@
     actual var type: PointerEventType = calculatePointerEventType()
         internal set
 
+    @OptIn(ExperimentalComposeUiApi::class)
     private fun calculatePointerEventType(): PointerEventType {
         val motionEvent = motionEvent
         if (motionEvent != null) {
@@ -61,6 +64,7 @@
                 MotionEvent.ACTION_MOVE -> PointerEventType.Move
                 MotionEvent.ACTION_HOVER_ENTER -> PointerEventType.Enter
                 MotionEvent.ACTION_HOVER_EXIT -> PointerEventType.Exit
+                ACTION_SCROLL -> PointerEventType.Scroll
 
                 else -> PointerEventType.Unknown
             }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.android.kt
index c3ee76b..3862b31 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.android.kt
@@ -22,4 +22,4 @@
     actual val uptime: Long,
     actual val pointers: List<PointerInputEventData>,
     val motionEvent: MotionEvent
-)
\ No newline at end of file
+)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 9c3862d..c4c6fc1 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -32,6 +32,7 @@
 import android.view.MotionEvent.ACTION_HOVER_ENTER
 import android.view.MotionEvent.ACTION_HOVER_EXIT
 import android.view.MotionEvent.ACTION_HOVER_MOVE
+import android.view.MotionEvent.ACTION_SCROLL
 import android.view.MotionEvent.ACTION_MOVE
 import android.view.MotionEvent.ACTION_POINTER_UP
 import android.view.MotionEvent.ACTION_UP
@@ -1016,6 +1017,14 @@
         if (autofillSupported()) _autofill?.performAutofill(values)
     }
 
+    override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
+        return if (event.actionMasked == ACTION_SCROLL) {
+            handleMotionEvent(event).dispatchedToAPointerInputModifier
+        } else {
+            super.dispatchGenericMotionEvent(event)
+        }
+    }
+
     // TODO(shepshapard): Test this method.
     override fun dispatchTouchEvent(motionEvent: MotionEvent): Boolean {
         if (hoverExitReceived) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/TempListUtils.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/TempListUtils.kt
index 940317c..d5c4d7c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/TempListUtils.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/TempListUtils.kt
@@ -27,6 +27,10 @@
  * to each pair of two adjacent elements in this collection.
  *
  * The returned list is empty if this collection contains less than two elements.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R> List<T>.fastZipWithNext(transform: (T, T) -> R): List<R> {
@@ -50,6 +54,10 @@
  * Throws an exception if this collection is empty. If the collection can be empty in an expected
  * way, please use [reduceOrNull] instead. It returns `null` when its receiver is empty.
  *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
+ *
  * @param [operation] function that takes current accumulator value and an element,
  * and calculates the next accumulator value.
  */
@@ -71,6 +79,10 @@
  * If any of two pairs would have the same key the last one gets added to the map.
  *
  * The returned map preserves the entry iteration order of the original collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, K, V> List<T>.fastAssociate(transform: (T) -> Pair<K, V>): Map<K, V> {
@@ -86,6 +98,10 @@
  * Returns a list of values built from the elements of `this` collection and the [other] collection with the same index
  * using the provided [transform] function applied to each pair of elements.
  * The returned list has length of the shortest collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R, V> List<T>.fastZip(
@@ -104,6 +120,10 @@
 /**
  * Returns a list containing the results of applying the given [transform] function
  * to each element in the original collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
@@ -122,6 +142,10 @@
  * If the collection could be huge, you can specify a non-negative value of [limit], in which case
  * only the first [limit] elements will be appended, followed by the [truncated] string (which
  * defaults to "...").
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 internal fun <T> List<T>.fastJoinToString(
     separator: CharSequence = ", ",
@@ -142,6 +166,10 @@
  * If the collection could be huge, you can specify a non-negative value of [limit], in which
  * case only the first [limit] elements will be appended, followed by the [truncated] string
  * (which defaults to "...").
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 private fun <T, A : Appendable> List<T>.fastJoinTo(
     buffer: A,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt
index b0d20eb..4a64979 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt
@@ -47,7 +47,8 @@
     val down: Boolean,
     val type: PointerType,
     val issuesEnterExit: Boolean = false,
-    val historical: List<HistoricalChange> = mutableListOf()
+    val historical: List<HistoricalChange> = mutableListOf(),
+    val scrollDelta: Offset = Offset.Zero
 )
 
 /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
index 53e6827..34632aa 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
@@ -27,7 +27,6 @@
 import androidx.compose.ui.input.pointer.PointerEventPass.Initial
 import androidx.compose.ui.input.pointer.PointerEventPass.Main
 import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.node.InternalCoreApi
 import androidx.compose.ui.unit.IntSize
 
 /**
@@ -108,7 +107,7 @@
 /**
  * Describes a pointer input change event that has occurred at a particular point in time.
  */
-expect class PointerEvent @OptIn(InternalCoreApi::class) internal constructor(
+expect class PointerEvent internal constructor(
     changes: List<PointerInputChange>,
     internalPointerEvent: InternalPointerEvent?
 ) {
@@ -263,7 +262,7 @@
 /**
  * The device type that produces a [PointerInputChange], such as a mouse or stylus.
  */
-inline class PointerType internal constructor(private val value: Int) {
+inline class PointerType private constructor(private val value: Int) {
 
     override fun toString(): String = when (value) {
         1 -> "Touch"
@@ -304,7 +303,7 @@
 /**
  * Indicates the primary reason that the [PointerEvent] was sent.
  */
-inline class PointerEventType(internal val value: Int) {
+inline class PointerEventType private constructor(internal val value: Int) {
     companion object {
         /**
          * An unknown reason for the event.
@@ -344,14 +343,25 @@
          * [Enter], [Exit], and [Enter] will be received.
          */
         val Exit = PointerEventType(5)
+
+        /**
+         * A scroll event was sent. This can happen, for example, due to a mouse scroll wheel.
+         * This event indicates that the [PointerInputChange.scrollDelta]'s [Offset] is non-zero.
+         */
+        @Suppress("EXPERIMENTAL_ANNOTATION_ON_WRONG_TARGET")
+        @ExperimentalComposeUiApi
+        @get:ExperimentalComposeUiApi
+        val Scroll = PointerEventType(6)
     }
 
+    @OptIn(ExperimentalComposeUiApi::class)
     override fun toString(): String = when (this) {
         Press -> "Press"
         Release -> "Release"
         Move -> "Move"
         Enter -> "Enter"
         Exit -> "Exit"
+        Scroll -> "Scroll"
         else -> "Unknown"
     }
 }
@@ -419,8 +429,41 @@
         get() = _historical ?: listOf()
     private var _historical: List<HistoricalChange>? = null
 
+    /**
+     * The amount of scroll wheel movement in the horizontal and vertical directions.
+     */
+    @Suppress("EXPERIMENTAL_ANNOTATION_ON_WRONG_TARGET")
     @ExperimentalComposeUiApi
-    constructor(
+    @get:ExperimentalComposeUiApi
+    val scrollDelta: Offset
+        get() = _scrollDelta
+    private var _scrollDelta: Offset = Offset.Zero
+
+    /**
+     * Indicates whether the change was consumed or not. Note that the change must be consumed in
+     * full as there's no partial consumption system provided.
+     */
+    @Suppress("EXPERIMENTAL_ANNOTATION_ON_WRONG_TARGET")
+    @ExperimentalComposeUiApi
+    @get:ExperimentalComposeUiApi
+    val isConsumed: Boolean
+        get() = consumed.downChange || consumed.positionChange
+
+    /**
+     * Consume change event, claiming all the corresponding change info to the caller. This is
+     * usually needed when, button, when being clicked, consumed the "up" event so no other parents
+     * of this button could consume this "up" again.
+     *
+     * "Consumption" is just an indication of the claim and each pointer input handler
+     * implementation must manually check this flag to respect it.
+     */
+    @ExperimentalComposeUiApi
+    fun consume() {
+        consumed.downChange = true
+        consumed.positionChange = true
+    }
+
+    internal constructor(
         id: PointerId,
         uptimeMillis: Long,
         position: Offset,
@@ -430,7 +473,8 @@
         previousPressed: Boolean,
         consumed: ConsumedData,
         type: PointerType,
-        historical: List<HistoricalChange>
+        historical: List<HistoricalChange>,
+        scrollDelta: Offset,
     ) : this(
         id,
         uptimeMillis,
@@ -443,6 +487,7 @@
         type
     ) {
         _historical = historical
+        _scrollDelta = scrollDelta
     }
 
     fun copy(
@@ -465,7 +510,8 @@
         previousPressed,
         consumed,
         type,
-        this.historical
+        this.historical,
+        this.scrollDelta
     )
 
     @ExperimentalComposeUiApi
@@ -490,7 +536,34 @@
         previousPressed,
         consumed,
         type,
-        historical
+        historical,
+        this.scrollDelta
+    )
+
+    internal fun copy(
+        id: PointerId = this.id,
+        currentTime: Long = this.uptimeMillis,
+        currentPosition: Offset = this.position,
+        currentPressed: Boolean = this.pressed,
+        previousTime: Long = this.previousUptimeMillis,
+        previousPosition: Offset = this.previousPosition,
+        previousPressed: Boolean = this.previousPressed,
+        consumed: ConsumedData = this.consumed,
+        type: PointerType = this.type,
+        historical: List<HistoricalChange> = this.historical,
+        scrollDelta: Offset
+    ): PointerInputChange = PointerInputChange(
+        id,
+        currentTime,
+        currentPosition,
+        currentPressed,
+        previousTime,
+        previousPosition,
+        previousPressed,
+        consumed,
+        type,
+        historical,
+        scrollDelta
     )
 
     override fun toString(): String {
@@ -503,7 +576,8 @@
             "previousPressed=$previousPressed, " +
             "consumed=$consumed, " +
             "type=$type, " +
-            "historical=$historical)"
+            "historical=$historical," +
+            "scrollDelta=$scrollDelta)"
     }
 }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
index e5f1347..20cf960 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
@@ -147,7 +147,8 @@
                     previousDown,
                     ConsumedData(),
                     it.type,
-                    it.historical
+                    it.historical,
+                    it.scrollDelta
                 )
             if (it.down) {
                 previousPointerInputData[it.id] = PointerInputData(
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
index d3d6f92..3c08f7a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
@@ -60,11 +60,8 @@
         }
     }
 
-    /**
-     * An initialization function that is called when the [LayoutNodeWrapper] is initially created,
-     * and also called when the [LayoutNodeWrapper] is re-used.
-     */
-    open fun onInitialize() {
+    override fun onInitialize() {
+        super.onInitialize()
         wrapped.wrappedBy = this
     }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawEntity.kt
similarity index 74%
rename from compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
rename to compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawEntity.kt
index 3f8536f..aa8d3a2c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DrawEntity.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2021 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.
@@ -21,13 +21,21 @@
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.toSize
 
-internal class ModifiedDrawNode(
-    wrapped: LayoutNodeWrapper,
-    drawModifier: DrawModifier
-) : DelegatingLayoutNodeWrapper<DrawModifier>(wrapped, drawModifier), OwnerScope {
+internal class DrawEntity(
+    val layoutNodeWrapper: LayoutNodeWrapper,
+    val modifier: DrawModifier
+) : OwnerScope {
+    private val layoutNode: LayoutNode
+        get() = layoutNodeWrapper.layoutNode
+
+    private val size: IntSize
+        get() = layoutNodeWrapper.size
+
+    var next: DrawEntity? = null
 
     private var cacheDrawModifier: DrawCacheModifier? = updateCacheDrawModifier()
 
@@ -38,7 +46,7 @@
 
         override val layoutDirection: LayoutDirection get() = layoutNode.layoutDirection
 
-        override val size: Size get() = measuredSize.toSize()
+        override val size: Size get() = layoutNodeWrapper.size.toSize()
     }
 
     // Flag to determine if the cache should be re-built
@@ -71,30 +79,30 @@
         }
     }
 
-    override fun onInitialize() {
-        super.onInitialize()
+    fun onInitialize() {
         cacheDrawModifier = updateCacheDrawModifier()
         invalidateCache = true
+        next?.onInitialize()
     }
 
-    override fun onMeasureResultChanged(width: Int, height: Int) {
-        super.onMeasureResultChanged(width, height)
+    fun onMeasureResultChanged(width: Int, height: Int) {
         invalidateCache = true
+        next?.onMeasureResultChanged(width, height)
     }
 
     // This is not thread safe
-    override fun performDraw(canvas: Canvas) {
-        val size = measuredSize.toSize()
+    fun draw(canvas: Canvas) {
+        val size = size.toSize()
         if (cacheDrawModifier != null && invalidateCache) {
             layoutNode.requireOwner().snapshotObserver.observeReads(
                 this,
-                onCommitAffectingModifiedDrawNode,
+                onCommitAffectingDrawEntity,
                 updateCache
             )
         }
 
         val drawScope = layoutNode.mDrawScope
-        drawScope.draw(canvas, size, wrapped) {
+        drawScope.draw(canvas, size, layoutNodeWrapper, this) {
             with(drawScope) {
                 with(modifier) {
                     draw()
@@ -107,15 +115,15 @@
         // Callback invoked whenever a state parameter that is read within the cache
         // execution callback is updated. This marks the cache flag as dirty and
         // invalidates the current layer.
-        private val onCommitAffectingModifiedDrawNode: (ModifiedDrawNode) -> Unit =
-            { modifiedDrawNode ->
-                if (modifiedDrawNode.isValid) {
-                    modifiedDrawNode.invalidateCache = true
-                    modifiedDrawNode.invalidateLayer()
+        private val onCommitAffectingDrawEntity: (DrawEntity) -> Unit =
+            { drawEntity ->
+                if (drawEntity.isValid) {
+                    drawEntity.invalidateCache = true
+                    drawEntity.layoutNodeWrapper.invalidateLayer()
                 }
             }
     }
 
     override val isValid: Boolean
-        get() = isAttached
-}
\ No newline at end of file
+        get() = layoutNodeWrapper.isAttached
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index ab6d3c9..71b3960 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -653,6 +653,8 @@
             val addedCallback = hasNewPositioningCallback()
             onPositionedCallbacks?.clear()
 
+            innerLayoutNodeWrapper.onInitialize()
+
             // Create a new chain of LayoutNodeWrappers, reusing existing ones from wrappers
             // when possible.
             val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
@@ -660,6 +662,13 @@
                     mod.onRemeasurementAvailable(this)
                 }
 
+                if (mod is DrawModifier) {
+                    val drawEntity = DrawEntity(toWrap, mod)
+                    drawEntity.next = toWrap.drawEntityHead
+                    toWrap.drawEntityHead = drawEntity
+                    drawEntity.onInitialize()
+                }
+
                 // Re-use the layoutNodeWrapper if possible.
                 reuseLayoutNodeWrapper(mod, toWrap)?.let {
                     return@foldOut it
@@ -686,10 +695,6 @@
                         .initialize()
                         .assignChained(toWrap)
                 }
-                if (mod is DrawModifier) {
-                    wrapper = ModifiedDrawNode(wrapper, mod)
-                        .initialize()
-                }
                 if (mod is FocusModifier) {
                     wrapper = ModifiedFocusNode(wrapper, mod)
                         .initialize()
@@ -1176,8 +1181,23 @@
         val infoList = mutableVectorOf<ModifierInfo>()
         forEachDelegate { wrapper ->
             wrapper as DelegatingLayoutNodeWrapper<*>
-            val info = ModifierInfo(wrapper.modifier, wrapper, wrapper.layer)
+            val layer = wrapper.layer
+            val info = ModifierInfo(wrapper.modifier, wrapper, layer)
             infoList += info
+            var node = wrapper.drawEntityHead // head
+            while (node != null) {
+                infoList += ModifierInfo(node.modifier, wrapper, layer)
+                node = node.next
+            }
+        }
+        var innerNode = innerLayoutNodeWrapper.drawEntityHead
+        while (innerNode != null) {
+            infoList += ModifierInfo(
+                innerNode.modifier,
+                innerLayoutNodeWrapper,
+                innerLayoutNodeWrapper.layer
+            )
+            innerNode = innerNode.next
         }
         return infoList.asMutableList()
     }
@@ -1243,7 +1263,9 @@
     private fun copyWrappersToCache() {
         forEachDelegate {
             wrapperCache += it as DelegatingLayoutNodeWrapper<*>
+            it.drawEntityHead = null
         }
+        innerLayoutNodeWrapper.drawEntityHead = null
     }
 
     private fun markReusedModifiers(modifier: Modifier) {
@@ -1330,7 +1352,7 @@
         forEachDelegateIncludingInner {
             if (it.layer != null) {
                 return false
-            } else if (it is ModifiedDrawNode) {
+            } else if (it.drawEntityHead != null) {
                 return true
             }
         }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt
index 8493379..de1893f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt
@@ -36,27 +36,36 @@
     // draw calls for all composables.
     // As a result there could be thread safety concerns here for multi-threaded drawing
     // scenarios, generally a single ComponentDrawScope should be shared for a particular thread
-    private var wrapped: LayoutNodeWrapper? = null
+    private var drawEntity: DrawEntity? = null
 
     override fun drawContent() {
-        drawIntoCanvas { canvas -> wrapped?.draw(canvas) }
+        drawIntoCanvas { canvas ->
+            val drawEntity = drawEntity!!
+            val nextDrawEntity = drawEntity.next
+            if (nextDrawEntity != null) {
+                nextDrawEntity.draw(canvas)
+            } else {
+                drawEntity.layoutNodeWrapper.performDraw(canvas)
+            }
+        }
     }
 
     internal inline fun draw(
         canvas: Canvas,
         size: Size,
-        LayoutNodeWrapper: LayoutNodeWrapper,
+        layoutNodeWrapper: LayoutNodeWrapper,
+        drawEntity: DrawEntity,
         block: DrawScope.() -> Unit
     ) {
-        val previousWrapper = wrapped
-        wrapped = LayoutNodeWrapper
+        val previousDrawEntity = this.drawEntity
+        this.drawEntity = drawEntity
         canvasDrawScope.draw(
-            LayoutNodeWrapper.measureScope,
-            LayoutNodeWrapper.measureScope.layoutDirection,
+            layoutNodeWrapper.measureScope,
+            layoutNodeWrapper.measureScope.layoutDirection,
             canvas,
             size,
             block
         )
-        wrapped = previousWrapper
+        this.drawEntity = previousDrawEntity
     }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index 7f82f7b..a240c7d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -154,6 +154,7 @@
         }
         layoutNode.owner?.onLayoutChange(layoutNode)
         measuredSize = IntSize(width, height)
+        drawEntityHead?.onMeasureResultChanged(width, height)
     }
 
     var position: IntOffset = IntOffset.Zero
@@ -190,6 +191,11 @@
 
     private val snapshotObserver get() = layoutNode.requireOwner().snapshotObserver
 
+    /**
+     * The head of the DrawEntity linked list
+     */
+    var drawEntityHead: DrawEntity? = null
+
     protected inline fun performingMeasure(
         constraints: Constraints,
         block: () -> Placeable
@@ -214,6 +220,14 @@
     }
 
     /**
+     * An initialization function that is called when the [LayoutNodeWrapper] is initially created,
+     * and also called when the [LayoutNodeWrapper] is re-used.
+     */
+    open fun onInitialize() {
+        layer?.invalidate()
+    }
+
+    /**
      * Places the modified child.
      */
     /*@CallSuper*/
@@ -252,12 +266,23 @@
             val x = position.x.toFloat()
             val y = position.y.toFloat()
             canvas.translate(x, y)
-            performDraw(canvas)
+            drawContainedDrawModifiers(canvas)
             canvas.translate(-x, -y)
         }
     }
 
-    protected abstract fun performDraw(canvas: Canvas)
+    private fun drawContainedDrawModifiers(canvas: Canvas) {
+        val head = drawEntityHead
+        if (head == null) {
+            performDraw(canvas)
+        } else {
+            head.draw(canvas)
+        }
+    }
+
+    open fun performDraw(canvas: Canvas) {
+        wrapped?.draw(canvas)
+    }
 
     open fun onPlaced() {}
 
@@ -266,7 +291,7 @@
     override fun invoke(canvas: Canvas) {
         if (layoutNode.isPlaced) {
             snapshotObserver.observeReads(this, onCommitAffectingLayer) {
-                performDraw(canvas)
+                drawContainedDrawModifiers(canvas)
             }
             lastLayerDrawingWasSkipped = false
         } else {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
index 1bc1644..775bdd3 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
@@ -256,6 +256,9 @@
 
     /**
      * Makes sure the passed [layoutNode] and its subtree is remeasured and has the final sizes.
+     *
+     * The node or some of the nodes in its subtree can still be kept unmeasured if they are
+     * not placed and don't affect the parent size. See [requestRemeasure] for details.
      */
     fun forceMeasureTheSubtree(layoutNode: LayoutNode) {
         // if there is nothing in `relayoutNodes` everything is remeasured.
@@ -273,8 +276,13 @@
                 remeasureAndRelayoutIfNeeded(child)
             }
 
-            // run recursively for the subtree.
-            forceMeasureTheSubtree(child)
+            // if the child is still in NeedsRemeasure state then this child remeasure wasn't
+            // needed. it can happen for example when this child is not placed and can't affect
+            // the parent size. we can skip the whole subtree.
+            if (child.layoutState != NeedsRemeasure) {
+                // run recursively for the subtree.
+                forceMeasureTheSubtree(child)
+            }
         }
 
         // if the child was resized during the remeasurement it could request a remeasure on
@@ -283,8 +291,6 @@
         if (layoutNode.layoutState == NeedsRemeasure && relayoutNodes.remove(layoutNode)) {
             remeasureAndRelayoutIfNeeded(layoutNode)
         }
-
-        require(layoutNode.layoutState != NeedsRemeasure)
     }
 
     /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierLocalConsumerNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierLocalConsumerNode.kt
index 8966754..6ab7e29 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierLocalConsumerNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierLocalConsumerNode.kt
@@ -27,7 +27,7 @@
 
     override fun onModifierChanged() {
         super.onModifierChanged()
-        if (isAttached) notifyConsumerOfChanges()
+        notifyConsumerOfChanges()
     }
 
     override fun attach() {
@@ -39,6 +39,10 @@
         get() = onModifierLocalRead(this)
 
     private fun notifyConsumerOfChanges() {
+        // If the node is not attached, we don't notify the consumers.
+        // Ultimately when the node is attached, this function will be called again.
+        if (!isAttached) return
+
         layoutNode.requireOwner().snapshotObserver.observeReads(this, onReadValuesChanged) {
             modifier.onModifierLocalsUpdated(this)
         }
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/ComposeScene.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/ComposeScene.desktop.kt
index 5ecc568..2a64b96 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/ComposeScene.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/ComposeScene.desktop.kt
@@ -47,7 +47,8 @@
     nativeEvent: Any?,
     type: PointerType,
     isMousePressed: Boolean,
-    pointerId: Long
+    pointerId: Long,
+    scrollDelta: Offset
 ): PointerInputEvent {
     return PointerInputEvent(
         eventType,
@@ -59,7 +60,8 @@
                 position,
                 position,
                 isMousePressed,
-                type
+                type,
+                scrollDelta = scrollDelta
             )
         ),
         nativeEvent as MouseEvent?
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeLayer.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeLayer.desktop.kt
index cd9aaf9..08c470d 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeLayer.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeLayer.desktop.kt
@@ -19,8 +19,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.mouse.MouseScrollOrientation
-import androidx.compose.ui.input.mouse.MouseScrollUnit
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.platform.PlatformComponent
@@ -267,19 +265,14 @@
 private fun ComposeScene.onMouseWheelEvent(
     density: Float,
     event: MouseWheelEvent
-) = with(event) {
-    sendPointerScrollEvent(
+) {
+    sendPointerEvent(
+        eventType = PointerEventType.Scroll,
         position = Offset(event.x.toFloat(), event.y.toFloat()) * density,
-        delta = if (scrollType == MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
-            MouseScrollUnit.Page((scrollAmount * preciseWheelRotation).toFloat())
+        scrollDelta = if (event.isShiftDown) {
+            Offset(event.preciseWheelRotation.toFloat(), 0f)
         } else {
-            MouseScrollUnit.Line((scrollAmount * preciseWheelRotation).toFloat())
-        },
-        // There are no other way to detect horizontal scrolling in AWT
-        orientation = if (isShiftDown) {
-            MouseScrollOrientation.Horizontal
-        } else {
-            MouseScrollOrientation.Vertical
+            Offset(0f, event.preciseWheelRotation.toFloat())
         },
         timeMillis = event.`when`,
         type = PointerType.Mouse,
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
index 309be44..2c74cda 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
@@ -46,7 +46,10 @@
     internal actual constructor(
         changes: List<PointerInputChange>,
         internalPointerEvent: InternalPointerEvent?
-    ) : this(changes, internalPointerEvent?.mouseEvent) {
+    ) : this(
+        changes,
+        internalPointerEvent?.mouseEvent
+    ) {
         this.type = internalPointerEvent?.type ?: PointerEventType.Unknown
     }
 
@@ -57,7 +60,10 @@
     /**
      * @param changes The changes.
      */
-    actual constructor(changes: List<PointerInputChange>) : this(changes, mouseEvent = null)
+    actual constructor(changes: List<PointerInputChange>) : this(
+        changes,
+        mouseEvent = null
+    )
 
     actual var type: PointerEventType = PointerEventType.Unknown
         internal set
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt
index 4eb942b..fc04147 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/TestComposeWindow.desktop.kt
@@ -24,6 +24,8 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.input.mouse.MouseScrollEvent
+import androidx.compose.ui.input.mouse.MouseScrollOrientation
+import androidx.compose.ui.input.mouse.MouseScrollUnit
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.node.RootForTest
 import androidx.compose.ui.unit.Constraints
@@ -34,7 +36,11 @@
 import org.jetbrains.skia.Surface
 import org.jetbrains.skiko.FrameDispatcher
 import java.awt.Component
+import java.awt.event.MouseWheelEvent
 import kotlin.coroutines.CoroutineContext
+import kotlin.math.abs
+import kotlin.math.roundToInt
+import kotlin.math.sign
 
 /**
  * A virtual window for testing purposes.
@@ -115,10 +121,36 @@
      */
     @OptIn(ExperimentalComposeUiApi::class)
     fun onMouseScroll(x: Int, y: Int, event: MouseScrollEvent) {
-        scene.sendPointerScrollEvent(
+        val delta = when (event.delta) {
+            is MouseScrollUnit.Line -> event.delta.value
+            is MouseScrollUnit.Page -> event.delta.value
+        }
+        val wheelRotation = sign(delta)
+        scene.sendPointerEvent(
+            eventType = PointerEventType.Scroll,
             position = Offset(x.toFloat(), y.toFloat()),
-            delta = event.delta,
-            orientation = event.orientation
+            scrollDelta = if (event.orientation == MouseScrollOrientation.Vertical) {
+                Offset(0f, wheelRotation)
+            } else {
+                Offset(wheelRotation, 0f)
+            },
+            nativeEvent = MouseWheelEvent(
+                EventComponent,
+                MouseWheelEvent.MOUSE_WHEEL,
+                0,
+                0,
+                0,
+                0,
+                0,
+                false,
+                if (event.delta is MouseScrollUnit.Line) {
+                    MouseWheelEvent.WHEEL_UNIT_SCROLL
+                } else {
+                    MouseWheelEvent.WHEEL_BLOCK_SCROLL
+                },
+                abs(delta.roundToInt()),
+                wheelRotation.roundToInt()
+            )
         )
     }
 
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt
index f3a83bd..c358016 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt
@@ -28,9 +28,6 @@
 import androidx.compose.runtime.withFrameNanos
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.key.KeyEvent as ComposeKeyEvent
-import androidx.compose.ui.input.mouse.MouseScrollEvent
-import androidx.compose.ui.input.mouse.MouseScrollOrientation
-import androidx.compose.ui.input.mouse.MouseScrollUnit
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.PointerInputEvent
 import androidx.compose.ui.input.pointer.PointerType
@@ -346,6 +343,7 @@
      *
      * @param eventType Indicates the primary reason that the event was sent.
      * @param position The [Offset] of the current pointer event, relative to the content.
+     * @param scrollDelta scroll delta for the PointerEventType.Scroll event
      * @param timeMillis The time of the current pointer event, in milliseconds. The start (`0`) time
      * is platform-dependent.
      * @param type The device type that produced the event, such as [mouse][PointerType.Mouse],
@@ -356,6 +354,7 @@
     fun sendPointerEvent(
         eventType: PointerEventType,
         position: Offset,
+        scrollDelta: Offset = Offset(0f, 0f),
         timeMillis: Long = System.nanoTime() / 1_000_000L,
         type: PointerType = PointerType.Mouse,
         nativeEvent: Any? = null,
@@ -369,7 +368,14 @@
             PointerEventType.Release -> isMousePressed = false
         }
         val event = pointerInputEvent(
-            eventType, position, timeMillis, nativeEvent, type, isMousePressed, pointerId
+            eventType,
+            position,
+            timeMillis,
+            nativeEvent,
+            type,
+            isMousePressed,
+            pointerId,
+            scrollDelta
         )
         when (eventType) {
             PointerEventType.Press -> onMousePressed(event)
@@ -380,56 +386,10 @@
             }
             PointerEventType.Enter -> hoveredOwner?.processPointerInput(event)
             PointerEventType.Exit -> hoveredOwner?.processPointerInput(event)
+            PointerEventType.Scroll -> hoveredOwner?.processPointerInput(event)
         }
     }
 
-    // TODO(demin): remove/change when we will have scroll event support in the common code
-    // TODO(demin): return Boolean (when it is consumed).
-    //  see ComposeLayer todo about AWTDebounceEventQueue
-    /**
-     * Send pointer scroll event to the content.
-     *
-     * @param position The [Offset] of the current pointer event, relative to the content
-     * @param delta Change of mouse scroll.
-     * Positive if scrolling down, negative if scrolling up.
-     * @param orientation Orientation in which scrolling event occurs.
-     * Up/down wheel scrolling causes events in vertical orientation.
-     * Left/right wheel scrolling causes events in horizontal orientation.
-     * @param timeMillis The time of the current pointer event, in milliseconds. The start (`0`) time
-     * is platform-dependent.
-     * @param type The device type that produced the event, such as [mouse][PointerType.Mouse],
-     * or [touch][PointerType.Touch].
-     * @param nativeEvent The original native event
-     */
-    @OptIn(ExperimentalComposeUiApi::class)
-    @Suppress("UNUSED_PARAMETER")
-    @ExperimentalComposeUiApi // it is more experimental than ComposeScene itself
-    fun sendPointerScrollEvent(
-        position: Offset,
-        delta: MouseScrollUnit,
-        orientation: MouseScrollOrientation = MouseScrollOrientation.Vertical,
-        timeMillis: Long = System.nanoTime() / 1_000_000L,
-        type: PointerType = PointerType.Mouse,
-        nativeEvent: Any? = null,
-//        buttons: PointerButtons? = null,
-//        keyboardModifiers: PointerKeyboardModifiers? = null,
-    ): Unit = postponeInvalidation {
-        check(!isDisposed) { "ComposeScene is disposed" }
-        hoveredOwner?.onMouseScroll(
-            position,
-            MouseScrollEvent(delta, orientation),
-            pointerInputEvent = pointerInputEvent(
-                PointerEventType.Unknown,
-                position,
-                timeMillis,
-                nativeEvent,
-                type,
-                isMousePressed,
-                pointerId
-            )
-        )
-    }
-
     private fun onMousePressed(event: PointerInputEvent) {
         val currentOwner = hoveredOwner
         if (currentOwner != null) {
@@ -471,5 +431,6 @@
     nativeEvent: Any?,
     type: PointerType,
     isMousePressed: Boolean,
-    pointerId: Long
+    pointerId: Long,
+    scrollDelta: Offset
 ): PointerInputEvent
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilter.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilter.skiko.kt
index 4cce567..cf9d2a0 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilter.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/input/mouse/MouseScrollFilter.skiko.kt
@@ -18,15 +18,14 @@
 
 package androidx.compose.ui.input.mouse
 
-import androidx.compose.runtime.remember
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.input.pointer.PointerInputModifier
-import androidx.compose.ui.input.pointer.PointerEvent
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerInputFilter
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.PointerType
+import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.unit.IntSize
+import java.awt.event.MouseWheelEvent
 
 /**
  * Indicates distance by which we should scroll some container.
@@ -78,6 +77,10 @@
     val orientation: MouseScrollOrientation
 )
 
+// TODO(demin): how easy-to-use scroll API should look like?
+//  maybe something like Modifier.pointerScroll { delta: Offset -> } ?
+//  or Modifier.pointerInput(Unit) { scroll { delta: Offset ->  } }
+//  ?
 /**
  * Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
  * allow it to intercept scroll events from mouse wheel and touchpad.
@@ -100,31 +103,40 @@
          */
         bounds: IntSize
     ) -> Boolean
-): Modifier = composed {
-    val filter = remember(::MouseScrollEventFilter)
-    filter.onMouseScroll = onMouseScroll
-    MousePointerInputModifierImpl(filter)
-}
-
-internal class MouseScrollEventFilter : PointerInputFilter() {
-    lateinit var onMouseScroll: (MouseScrollEvent, IntSize) -> Boolean
-
-    override fun onPointerEvent(
-        pointerEvent: PointerEvent,
-        pass: PointerEventPass,
-        bounds: IntSize
-    ) = Unit
-
-    override fun onCancel() = Unit
-
-    fun onMouseScroll(event: MouseScrollEvent): Boolean {
-        return isAttached && onMouseScroll(event, size)
+): Modifier = pointerInput(onMouseScroll) {
+    awaitPointerEventScope {
+        while (true) {
+            val event = awaitPointerEvent()
+            val mouseEvent = (event.mouseEvent as? MouseWheelEvent) ?: continue
+            val mouseChange = event.changes.find { it.type == PointerType.Mouse }
+            val isScroll = event.type == PointerEventType.Scroll
+            if (isScroll && mouseChange != null && !mouseChange.isConsumed) {
+                val legacyEvent = mouseEvent.toLegacyEvent(mouseChange.scrollDelta)
+                if (onMouseScroll(legacyEvent, size)) {
+                    mouseChange.consume()
+                }
+            }
+        }
     }
 }
 
-private data class MousePointerInputModifierImpl(
-    override val pointerInputFilter: PointerInputFilter
-) : PointerInputModifier
+private fun MouseWheelEvent.toLegacyEvent(scrollDelta: Offset): MouseScrollEvent {
+    val value = if (scrollDelta.x != 0f) scrollDelta.x else scrollDelta.y
+    return MouseScrollEvent(
+        delta = if (scrollType == MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
+            MouseScrollUnit.Page(value * scrollAmount)
+        } else {
+            MouseScrollUnit.Line(value * scrollAmount)
+        },
+
+        // There are no other way to detect horizontal scrolling in AWT
+        orientation = if (isShiftDown || scrollDelta.x != 0f) {
+            MouseScrollOrientation.Horizontal
+        } else {
+            MouseScrollOrientation.Vertical
+        }
+    )
+}
 
 @ExperimentalComposeUiApi
 enum class MouseScrollOrientation {
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
index a3d80e6..9f68c30 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
@@ -47,20 +47,16 @@
 import androidx.compose.ui.input.key.isShiftPressed
 import androidx.compose.ui.input.key.key
 import androidx.compose.ui.input.key.type
-import androidx.compose.ui.input.mouse.MouseScrollEvent
-import androidx.compose.ui.input.mouse.MouseScrollEventFilter
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.PointerIcon
 import androidx.compose.ui.input.pointer.PointerIconDefaults
 import androidx.compose.ui.input.pointer.PointerIconService
 import androidx.compose.ui.input.pointer.PointerInputEvent
 import androidx.compose.ui.input.pointer.PointerInputEventProcessor
-import androidx.compose.ui.input.pointer.PointerInputFilter
 import androidx.compose.ui.input.pointer.PositionCalculator
 import androidx.compose.ui.input.pointer.ProcessResult
 import androidx.compose.ui.input.pointer.TestPointerInputEventData
 import androidx.compose.ui.layout.RootMeasurePolicy
-import androidx.compose.ui.node.HitTestResult
 import androidx.compose.ui.node.InternalCoreApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.LayoutNodeDrawScope
@@ -405,31 +401,6 @@
         )
     }
 
-    // TODO(demin): This is likely temporary. After PointerInputEvent can handle mouse events
-    //  (scroll in particular), we can replace it with processPointerInput. see b/166105940
-    internal fun onMouseScroll(
-        position: Offset,
-        event: MouseScrollEvent,
-        pointerInputEvent: PointerInputEvent
-    ) {
-        measureAndLayout()
-        sendSyntheticEvents()
-        lastPointerEvent = pointerInputEvent
-
-        val inputFilters = HitTestResult<PointerInputFilter>()
-        root.hitTest(position, inputFilters)
-
-        for (
-            filter in inputFilters
-                .asReversed()
-                .asSequence()
-                .filterIsInstance<MouseScrollEventFilter>()
-        ) {
-            val isConsumed = filter.onMouseScroll(event)
-            if (isConsumed) break
-        }
-    }
-
     override val pointerIconService: PointerIconService =
         object : PointerIconService {
             override var current: PointerIcon
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/PointerInputTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/PointerInputTest.kt
index 353cc36..d2ca364 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/PointerInputTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/PointerInputTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.input.pointer
 
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.geometry.Offset
 import com.google.common.truth.FailureMetadata
 import com.google.common.truth.Subject
@@ -347,6 +348,16 @@
         assertThat(pointerInputChange.anyChangeConsumed()).isFalse()
     }
 
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun consume_noChanges_returnsTrue() {
+        val pointerInputChange =
+            createPointerInputChange(8f, 16f, true, 8f, 16f, true, 0f, 0f, false)
+        pointerInputChange.consume()
+        assertThat(pointerInputChange.isConsumed).isTrue()
+        assertThat(pointerInputChange.anyChangeConsumed()).isTrue()
+    }
+
     @Test
     fun anyChangeConsumed_downConsumed_returnsTrue() {
         val pointerInputChange =
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index 7475387..6c5a986 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -681,8 +681,8 @@
     @Test
     fun coordinatesAttachedWhenLayoutNodeAttached() {
         val layoutNode = LayoutNode()
-        val drawModifier = Modifier.drawBehind { }
-        layoutNode.modifier = drawModifier
+        val layoutModifier = Modifier.graphicsLayer { }
+        layoutNode.modifier = layoutModifier
         assertFalse(layoutNode.coordinates.isAttached)
         assertFalse(layoutNode.coordinates.isAttached)
         layoutNode.attach(MockOwner())
@@ -697,16 +697,16 @@
     @Test
     fun layoutNodeWrapperSameWithReplacementModifier() {
         val layoutNode = LayoutNode()
-        val drawModifier = Modifier.drawBehind { }
+        val layoutModifier = Modifier.graphicsLayer { }
 
-        layoutNode.modifier = drawModifier
+        layoutNode.modifier = layoutModifier
         val oldLayoutNodeWrapper = layoutNode.outerLayoutNodeWrapper
         assertFalse(oldLayoutNodeWrapper.isAttached)
 
         layoutNode.attach(MockOwner())
         assertTrue(oldLayoutNodeWrapper.isAttached)
 
-        layoutNode.modifier = Modifier.drawBehind { }
+        layoutNode.modifier = Modifier.graphicsLayer { }
         val newLayoutNodeWrapper = layoutNode.outerLayoutNodeWrapper
         assertSame(newLayoutNodeWrapper, oldLayoutNodeWrapper)
     }
@@ -741,9 +741,9 @@
     @Test
     fun layoutNodeWrapperAttachedWhenLayoutNodeAttached() {
         val layoutNode = LayoutNode()
-        val drawModifier = Modifier.drawBehind { }
+        val layoutModifier = Modifier.graphicsLayer { }
 
-        layoutNode.modifier = drawModifier
+        layoutNode.modifier = layoutModifier
         val oldLayoutNodeWrapper = layoutNode.outerLayoutNodeWrapper
         assertFalse(oldLayoutNodeWrapper.isAttached)
 
@@ -760,8 +760,8 @@
     fun layoutNodeWrapperParentLayoutCoordinates() {
         val layoutNode = LayoutNode()
         val layoutNode2 = LayoutNode()
-        val drawModifier = Modifier.drawBehind { }
-        layoutNode.modifier = drawModifier
+        val layoutModifier = Modifier.graphicsLayer { }
+        layoutNode.modifier = layoutModifier
         layoutNode2.insertAt(0, layoutNode)
         layoutNode2.attach(MockOwner())
 
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierLocalConsumerNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierLocalConsumerNodeTest.kt
new file mode 100644
index 0000000..4cc4663
--- /dev/null
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierLocalConsumerNodeTest.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.autofill.Autofill
+import androidx.compose.ui.autofill.AutofillTree
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.input.InputModeManager
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.pointer.PointerIconService
+import androidx.compose.ui.modifier.modifierLocalConsumer
+import androidx.compose.ui.modifier.modifierLocalOf
+import androidx.compose.ui.modifier.modifierLocalProvider
+import androidx.compose.ui.platform.AccessibilityManager
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.platform.ViewConfiguration
+import androidx.compose.ui.platform.WindowInfo
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.input.TextInputService
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalComposeUiApi::class)
+@RunWith(JUnit4::class)
+class ModifierLocalConsumerNodeTest {
+
+    private val default = "Default"
+    private val ModifierLocalString = modifierLocalOf { "Default" }
+
+    private val owner = FakeOwner()
+    private val layoutNode = LayoutNode()
+
+    @Test
+    fun `unattached modifier local consumer does not invoke lambda`() {
+        // Arrange.
+        var receivedValue = ""
+        TestBox(Modifier.modifierLocalConsumer { receivedValue = ModifierLocalString.current })
+
+        // Assert.
+        assertThat(receivedValue).isEmpty()
+    }
+
+    @Test
+    fun `attached modifier local consumer with no provider reads default value`() {
+        // Arrange.
+        lateinit var receivedValue: String
+        TestBox(Modifier.modifierLocalConsumer { receivedValue = ModifierLocalString.current })
+
+        // Act.
+        attach()
+
+        // Assert.
+        assertThat(receivedValue).isEqualTo(default)
+    }
+
+    @Test
+    fun `changing the consumer modifier with no provider reads default value`() {
+        // Arrange.
+        lateinit var receivedValue: String
+        TestBox(Modifier.modifierLocalConsumer { receivedValue = ModifierLocalString.current })
+        attach()
+        receivedValue = ""
+
+        // Act.
+        changeModifier(Modifier.modifierLocalConsumer {
+            receivedValue = ModifierLocalString.current
+        })
+
+        // Assert.
+        assertThat(receivedValue).isEqualTo(default)
+    }
+
+    @Test
+    fun `detached modifier local consumer with no provider does not invoke lambda`() {
+        // Arrange.
+        lateinit var receivedValue: String
+        TestBox(Modifier.modifierLocalConsumer { receivedValue = ModifierLocalString.current })
+        attach()
+        receivedValue = ""
+
+        // Act.
+        detach()
+
+        // Assert.
+        assertThat(receivedValue).isEmpty()
+    }
+
+    @Test
+    fun `unattached modifier local consumer with provider does not invoke lambda`() {
+        // Arrange.
+        var receivedValue = ""
+        TestBox(
+            modifier = Modifier
+                .modifierLocalProvider(ModifierLocalString) { "Initial Value" }
+                .modifierLocalConsumer { receivedValue = ModifierLocalString.current }
+        )
+
+        // Assert.
+        assertThat(receivedValue).isEmpty()
+    }
+
+    @Test
+    fun `attached modifier local consumer with provider reads provided value`() {
+        // Arrange.
+        val providedValue = "Provided Value"
+        lateinit var receivedValue: String
+        TestBox(
+            modifier = Modifier
+                .modifierLocalProvider(ModifierLocalString) { providedValue }
+                .modifierLocalConsumer { receivedValue = ModifierLocalString.current }
+        )
+        // Act.
+        attach()
+
+        // Assert.
+        assertThat(receivedValue).isEqualTo(providedValue)
+    }
+
+    @Test
+    fun `changing provided value causes consumer to receive new provided value`() {
+        // Arrange.
+        val initialValue = "Initial Value"
+        val finalValue = "Final Value"
+        var providedValue by mutableStateOf(initialValue)
+        lateinit var receivedValue: String
+        TestBox(
+            modifier = Modifier
+                .modifierLocalProvider(ModifierLocalString) { providedValue }
+                .modifierLocalConsumer { receivedValue = ModifierLocalString.current }
+        )
+        attach()
+
+        // Act.
+        Snapshot.withMutableSnapshot {
+            providedValue = finalValue
+        }
+
+        // Assert.
+        assertThat(receivedValue).isEqualTo(finalValue)
+    }
+
+    @Test
+    fun `changing provided value after detaching modifier does not invoke consumer lambda`() {
+        // Arrange.
+        val initialValue = "Initial Value"
+        val finalValue = "Final Value"
+        var providedValue by mutableStateOf(initialValue)
+        lateinit var receivedValue: String
+        TestBox(
+            modifier = Modifier
+                .modifierLocalProvider(ModifierLocalString) { providedValue }
+                .modifierLocalConsumer { receivedValue = ModifierLocalString.current }
+        )
+        attach()
+        detach()
+        receivedValue = ""
+
+        // Act.
+        Snapshot.withMutableSnapshot {
+            providedValue = finalValue
+        }
+
+        // Assert.
+        assertThat(receivedValue).isEmpty()
+    }
+
+    @Test
+    fun `changing modifiers after detaching modifier does not invoke consumer lambda`() {
+        // Arrange.
+        lateinit var receivedValue: String
+        TestBox(
+            modifier = Modifier
+                .modifierLocalProvider(ModifierLocalString) { "Provided Value" }
+                .modifierLocalConsumer { receivedValue = ModifierLocalString.current }
+        )
+        attach()
+        detach()
+        receivedValue = ""
+
+        // Act.
+        changeModifier(Modifier.modifierLocalConsumer {
+            receivedValue = ModifierLocalString.current
+        })
+
+        // Assert.
+        assertThat(receivedValue).isEmpty()
+    }
+
+    @Test
+    fun `changing the consumer modifier with provider reads provided value`() {
+        // Arrange.
+        val providedValue = "Provided Value"
+        lateinit var receivedValue: String
+        TestBox(
+            modifier = Modifier
+                .modifierLocalProvider(ModifierLocalString) { providedValue }
+                .modifierLocalConsumer { receivedValue = ModifierLocalString.current }
+        )
+        attach()
+        receivedValue = ""
+
+        // Act.
+        changeModifier(
+            Modifier
+                .modifierLocalProvider(ModifierLocalString) { providedValue }
+                .modifierLocalConsumer { receivedValue = ModifierLocalString.current }
+        )
+
+        // Assert.
+        assertThat(receivedValue).isEqualTo(providedValue)
+    }
+
+    @Test
+    fun `detached modifier local consumer with provider does not invoke lambda`() {
+        // Arrange.
+        lateinit var receivedValue: String
+        TestBox(
+            modifier = Modifier
+                .modifierLocalProvider(ModifierLocalString) { "Provided Value" }
+                .modifierLocalConsumer { receivedValue = ModifierLocalString.current }
+        )
+        attach()
+        receivedValue = ""
+
+        // Act.
+        detach()
+
+        // Assert.
+        assertThat(receivedValue).isEmpty()
+    }
+
+    private fun TestBox(modifier: Modifier = Modifier) {
+        owner.snapshotObserver.startObserving()
+        layoutNode.modifier = modifier
+    }
+
+    private fun attach() {
+        layoutNode.attach(owner)
+    }
+
+    private fun detach() {
+        layoutNode.detach()
+    }
+
+    private fun changeModifier(modifier: Modifier) {
+        with(layoutNode) {
+            if (isAttached) { forEachLayoutNodeWrapper { it.detach() } }
+            this.modifier = modifier
+            if (isAttached) { forEachLayoutNodeWrapper { it.attach() } }
+        }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    private class FakeOwner : Owner {
+        @OptIn(InternalCoreApi::class)
+        override var showLayoutBounds: Boolean = false
+        override val snapshotObserver: OwnerSnapshotObserver = OwnerSnapshotObserver { it.invoke() }
+        override fun onRequestMeasure(layoutNode: LayoutNode) {}
+        override fun onAttach(node: LayoutNode) = node.forEachLayoutNodeWrapper { it.attach() }
+        override fun onDetach(node: LayoutNode) = node.forEachLayoutNodeWrapper { it.detach() }
+
+        override val root: LayoutNode
+            get() = TODO("Not yet implemented")
+        override val sharedDrawScope: LayoutNodeDrawScope
+            get() = TODO("Not yet implemented")
+        override val rootForTest: RootForTest
+            get() = TODO("Not yet implemented")
+        override val hapticFeedBack: HapticFeedback
+            get() = TODO("Not yet implemented")
+        override val inputModeManager: InputModeManager
+            get() = TODO("Not yet implemented")
+        override val clipboardManager: ClipboardManager
+            get() = TODO("Not yet implemented")
+        override val accessibilityManager: AccessibilityManager
+            get() = TODO("Not yet implemented")
+        override val textToolbar: TextToolbar
+            get() = TODO("Not yet implemented")
+        override val density: Density
+            get() = TODO("Not yet implemented")
+        override val textInputService: TextInputService
+            get() = TODO("Not yet implemented")
+        override val pointerIconService: PointerIconService
+            get() = TODO("Not yet implemented")
+        override val focusManager: FocusManager
+            get() = TODO("Not yet implemented")
+        override val windowInfo: WindowInfo
+            get() = TODO("Not yet implemented")
+        override val fontLoader: Font.ResourceLoader
+            get() = TODO("Not yet implemented")
+        override val layoutDirection: LayoutDirection
+            get() = TODO("Not yet implemented")
+        override val measureIteration: Long
+            get() = TODO("Not yet implemented")
+        override val viewConfiguration: ViewConfiguration
+            get() = TODO("Not yet implemented")
+        override val autofillTree: AutofillTree
+            get() = TODO("Not yet implemented")
+        override val autofill: Autofill
+            get() = TODO("Not yet implemented")
+
+        override fun createLayer(drawBlock: (Canvas) -> Unit, invalidateParentLayer: () -> Unit) =
+            TODO("Not yet implemented")
+        override fun onRequestRelayout(layoutNode: LayoutNode) =
+            TODO("Not yet implemented")
+        override fun calculatePositionInWindow(localPosition: Offset) =
+            TODO("Not yet implemented")
+        override fun calculateLocalPosition(positionInWindow: Offset) =
+            TODO("Not yet implemented")
+        override fun requestFocus() =
+            TODO("Not yet implemented")
+        override fun measureAndLayout(sendPointerUpdate: Boolean) =
+            TODO("Not yet implemented")
+        override fun forceMeasureTheSubtree(layoutNode: LayoutNode) =
+            TODO("Not yet implemented")
+        override fun onSemanticsChange() =
+            TODO("Not yet implemented")
+        override fun onLayoutChange(layoutNode: LayoutNode) =
+            TODO("Not yet implemented")
+        override fun getFocusDirection(keyEvent: KeyEvent) =
+            TODO("Not yet implemented")
+    }
+}
+
+private fun LayoutNode.forEachLayoutNodeWrapper(action: (LayoutNodeWrapper) -> Unit) {
+    var layoutNodeWrapper: LayoutNodeWrapper? = outerLayoutNodeWrapper
+    while (layoutNodeWrapper != null) {
+        action.invoke(layoutNodeWrapper)
+        layoutNodeWrapper = layoutNodeWrapper.wrapped
+    }
+}
diff --git a/core/core-ktx/api/current.txt b/core/core-ktx/api/current.txt
index fee3502..86d91a7 100644
--- a/core/core-ktx/api/current.txt
+++ b/core/core-ktx/api/current.txt
@@ -451,8 +451,10 @@
   public final class SizeKt {
     method @RequiresApi(21) public static inline operator int component1(android.util.Size);
     method @RequiresApi(21) public static inline operator float component1(android.util.SizeF);
+    method public static inline operator float component1(androidx.core.util.SizeFCompat);
     method @RequiresApi(21) public static inline operator int component2(android.util.Size);
     method @RequiresApi(21) public static inline operator float component2(android.util.SizeF);
+    method public static inline operator float component2(androidx.core.util.SizeFCompat);
   }
 
   public final class SparseArrayKt {
diff --git a/core/core-ktx/api/public_plus_experimental_current.txt b/core/core-ktx/api/public_plus_experimental_current.txt
index fee3502..86d91a7 100644
--- a/core/core-ktx/api/public_plus_experimental_current.txt
+++ b/core/core-ktx/api/public_plus_experimental_current.txt
@@ -451,8 +451,10 @@
   public final class SizeKt {
     method @RequiresApi(21) public static inline operator int component1(android.util.Size);
     method @RequiresApi(21) public static inline operator float component1(android.util.SizeF);
+    method public static inline operator float component1(androidx.core.util.SizeFCompat);
     method @RequiresApi(21) public static inline operator int component2(android.util.Size);
     method @RequiresApi(21) public static inline operator float component2(android.util.SizeF);
+    method public static inline operator float component2(androidx.core.util.SizeFCompat);
   }
 
   public final class SparseArrayKt {
diff --git a/core/core-ktx/api/restricted_current.txt b/core/core-ktx/api/restricted_current.txt
index fee3502..86d91a7 100644
--- a/core/core-ktx/api/restricted_current.txt
+++ b/core/core-ktx/api/restricted_current.txt
@@ -451,8 +451,10 @@
   public final class SizeKt {
     method @RequiresApi(21) public static inline operator int component1(android.util.Size);
     method @RequiresApi(21) public static inline operator float component1(android.util.SizeF);
+    method public static inline operator float component1(androidx.core.util.SizeFCompat);
     method @RequiresApi(21) public static inline operator int component2(android.util.Size);
     method @RequiresApi(21) public static inline operator float component2(android.util.SizeF);
+    method public static inline operator float component2(androidx.core.util.SizeFCompat);
   }
 
   public final class SparseArrayKt {
diff --git a/core/core-ktx/src/androidTest/java/androidx/core/util/SizeTest.kt b/core/core-ktx/src/androidTest/java/androidx/core/util/SizeTest.kt
index 5cd063b..07aa580 100644
--- a/core/core-ktx/src/androidTest/java/androidx/core/util/SizeTest.kt
+++ b/core/core-ktx/src/androidTest/java/androidx/core/util/SizeTest.kt
@@ -23,18 +23,25 @@
 import org.junit.Assert.assertEquals
 import org.junit.Test
 
-@SdkSuppress(minSdkVersion = 21)
 @SmallTest
 class SizeTest {
+    @SdkSuppress(minSdkVersion = 21)
     @Test fun destructuringSize() {
         val (w, h) = Size(320, 240)
         assertEquals(320, w)
         assertEquals(240, h)
     }
 
+    @SdkSuppress(minSdkVersion = 21)
     @Test fun destructuringSizeF() {
         val (w, h) = SizeF(1920.0f, 1080.0f)
         assertEquals(1920.0f, w)
         assertEquals(1080.0f, h)
     }
+
+    @Test fun destructuringSizeFCompat() {
+        val (w, h) = SizeFCompat(1920.0f, 1080.0f)
+        assertEquals(1920.0f, w)
+        assertEquals(1080.0f, h)
+    }
 }
diff --git a/core/core-ktx/src/main/java/androidx/core/util/Size.kt b/core/core-ktx/src/main/java/androidx/core/util/Size.kt
index ab2d80a..5cb85db 100644
--- a/core/core-ktx/src/main/java/androidx/core/util/Size.kt
+++ b/core/core-ktx/src/main/java/androidx/core/util/Size.kt
@@ -71,3 +71,25 @@
  */
 @RequiresApi(21)
 public inline operator fun SizeF.component2(): Float = height
+
+/**
+ * Returns "width", the first component of this [SizeFCompat].
+ *
+ * This method allows to use destructuring declarations when working with
+ * sizes, for example:
+ * ```
+ * val (w, h) = mySize
+ * ```
+ */
+public inline operator fun SizeFCompat.component1(): Float = width
+
+/**
+ * Returns "height", the second component of this [SizeFCompat].
+ *
+ * This method allows to use destructuring declarations when working with
+ * sizes, for example:
+ * ```
+ * val (w, h) = mySize
+ * ```
+ */
+public inline operator fun SizeFCompat.component2(): Float = height
diff --git a/core/core-performance/build.gradle b/core/core-performance/build.gradle
index 576a8d8..add1f93 100644
--- a/core/core-performance/build.gradle
+++ b/core/core-performance/build.gradle
@@ -29,7 +29,7 @@
     testImplementation(libs.kotlinStdlib)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
-    testImplementation(libs.robolectric)
+    testImplementation("org.robolectric:robolectric:4.7") // TODO(b/206673076): use libs.robolectric
 }
 
 androidx {
diff --git a/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.kt b/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.kt
index afe7ad1..246f23a 100644
--- a/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.kt
+++ b/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.kt
@@ -35,7 +35,7 @@
      * <p>
      * Defaults to {@link Build.MEDIA_PERFORMANCE_CLASS}
      */
-    fun getPerformanceClass(): Int {
+    fun getMediaPerformanceClass(): Int {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             return Build.VERSION.MEDIA_PERFORMANCE_CLASS
         }
diff --git a/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt b/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt
index 23bcaf6..895b376 100644
--- a/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt
+++ b/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt
@@ -19,11 +19,11 @@
 import android.os.Build.VERSION_CODES.R
 import android.os.Build.VERSION_CODES.S
 import com.google.common.truth.Truth.assertThat
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
+import org.robolectric.shadows.ShadowBuild
 import org.robolectric.shadows.ShadowSystemProperties
 
 /** Unit tests for [PerformanceClass]. */
@@ -34,17 +34,24 @@
 
     @Test
     @Config(maxSdk = R)
-    fun getPerformanceClass_sdk30() {
-        assertThat(pc.getPerformanceClass()).isEqualTo(0)
+    fun getMediaPerformanceClass_sdk30() {
+        assertThat(pc.getMediaPerformanceClass()).isEqualTo(0)
     }
 
     @Test
-    // Note this test is not actually running because robolectric does not support sdk31 yet
-    @Ignore("b/206673076")
     @Config(minSdk = S)
-    fun getPerformanceClass_sdk31() {
+    fun getMediaPerformanceClass_sdk31_declared30() {
         // TODO(b/205732671): Use ShadowBuild.setMediaPerformanceClass when available
-        ShadowSystemProperties.override("ro.odm.build.media_performance_class", "31")
-        assertThat(pc.getPerformanceClass()).isEqualTo(31)
+        ShadowSystemProperties.override("ro.odm.build.media_performance_class", "30")
+        ShadowBuild.reset()
+        assertThat(pc.getMediaPerformanceClass()).isEqualTo(30)
+    }
+
+    @Test
+    @Config(minSdk = S)
+    fun getMediaPerformanceClass_sdk31_notDeclared() {
+        // TODO(b/205732671): Use ShadowBuild.setMediaPerformanceClass when available
+        ShadowBuild.reset()
+        assertThat(pc.getMediaPerformanceClass()).isEqualTo(0)
     }
 }
\ No newline at end of file
diff --git a/core/core-splashscreen/src/main/java/androidx/core/splashscreen/SplashScreen.kt b/core/core-splashscreen/src/main/java/androidx/core/splashscreen/SplashScreen.kt
index 57fef19..46694db 100644
--- a/core/core-splashscreen/src/main/java/androidx/core/splashscreen/SplashScreen.kt
+++ b/core/core-splashscreen/src/main/java/androidx/core/splashscreen/SplashScreen.kt
@@ -65,7 +65,7 @@
          * This needs to be called before [Activity.setContentView] or other view operation on
          * the root view (e.g setting flags).
          *
-         * Alternatively, if a [SplashScreen] instance is not required, the them can manually be
+         * Alternatively, if a [SplashScreen] instance is not required, the theme can manually be
          * set using [Activity.setTheme].
          */
         @JvmStatic
@@ -343,4 +343,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index e2580ac..2d9d03a 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -2030,6 +2030,14 @@
     method public boolean test(T!);
   }
 
+  public final class SizeFCompat {
+    ctor public SizeFCompat(float, float);
+    method @RequiresApi(21) public static androidx.core.util.SizeFCompat createFromSizeF(android.util.SizeF);
+    method public float getHeight();
+    method public float getWidth();
+    method @RequiresApi(21) public android.util.SizeF toSizeF();
+  }
+
   public interface Supplier<T> {
     method public T! get();
   }
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 98fa24a..c7d450fb 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -2034,6 +2034,14 @@
     method public boolean test(T!);
   }
 
+  public final class SizeFCompat {
+    ctor public SizeFCompat(float, float);
+    method @RequiresApi(21) public static androidx.core.util.SizeFCompat createFromSizeF(android.util.SizeF);
+    method public float getHeight();
+    method public float getWidth();
+    method @RequiresApi(21) public android.util.SizeF toSizeF();
+  }
+
   public interface Supplier<T> {
     method public T! get();
   }
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 65f68dd..39514e4 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -2400,6 +2400,7 @@
     method public static void checkArgument(boolean);
     method public static void checkArgument(boolean, Object);
     method public static void checkArgument(boolean, String, java.lang.Object!...);
+    method public static float checkArgumentFinite(float, String);
     method public static int checkArgumentInRange(int, int, int, String);
     method public static long checkArgumentInRange(long, long, long, String);
     method public static float checkArgumentInRange(float, float, float, String);
@@ -2420,6 +2421,14 @@
     method public boolean test(T!);
   }
 
+  public final class SizeFCompat {
+    ctor public SizeFCompat(float, float);
+    method @RequiresApi(21) public static androidx.core.util.SizeFCompat createFromSizeF(android.util.SizeF);
+    method public float getHeight();
+    method public float getWidth();
+    method @RequiresApi(21) public android.util.SizeF toSizeF();
+  }
+
   public interface Supplier<T> {
     method public T! get();
   }
diff --git a/core/core/src/main/java/androidx/core/util/Preconditions.java b/core/core/src/main/java/androidx/core/util/Preconditions.java
index ce979e0..3461d12 100644
--- a/core/core/src/main/java/androidx/core/util/Preconditions.java
+++ b/core/core/src/main/java/androidx/core/util/Preconditions.java
@@ -336,6 +336,29 @@
         return value;
     }
 
+    /**
+     * Ensures that the argument floating point value is a finite number.
+     *
+     * <p>A finite number is defined to be both representable (that is, not NaN) and
+     * not infinite (that is neither positive or negative infinity).</p>
+     *
+     * @param value a floating point value
+     * @param valueName the name of the argument to use if the check fails
+     *
+     * @return the validated floating point value
+     *
+     * @throws IllegalArgumentException if {@code value} was not finite
+     */
+    public static float checkArgumentFinite(float value, @NonNull String valueName) {
+        if (Float.isNaN(value)) {
+            throw new IllegalArgumentException(valueName + " must not be NaN");
+        } else if (Float.isInfinite(value)) {
+            throw new IllegalArgumentException(valueName + " must not be infinite");
+        }
+
+        return value;
+    }
+
     private Preconditions() {
     }
 }
diff --git a/core/core/src/main/java/androidx/core/util/SizeFCompat.java b/core/core/src/main/java/androidx/core/util/SizeFCompat.java
new file mode 100644
index 0000000..124a1ec
--- /dev/null
+++ b/core/core/src/main/java/androidx/core/util/SizeFCompat.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2021 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.core.util;
+
+import android.util.SizeF;
+
+import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+/**
+ * Immutable class for describing width and height dimensions in some arbitrary unit. Width and
+ * height are finite values stored as a floating point representation.
+ * <p>
+ * This is a backward-compatible version of {@link SizeF}.
+ */
+public final class SizeFCompat {
+
+    private final float mWidth;
+    private final float mHeight;
+
+    public SizeFCompat(float width, float height) {
+        mWidth = Preconditions.checkArgumentFinite(width, "width");
+        mHeight = Preconditions.checkArgumentFinite(height, "height");
+    }
+
+    /**
+     * Get the width of the size (as an arbitrary unit).
+     * @return width
+     */
+    public float getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * Get the height of the size (as an arbitrary unit).
+     * @return height
+     */
+    public float getHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SizeFCompat)) return false;
+        SizeFCompat that = (SizeFCompat) o;
+        return that.mWidth == mWidth && that.mHeight == mHeight;
+    }
+
+    @Override
+    public int hashCode() {
+        return Float.floatToIntBits(mWidth) ^ Float.floatToIntBits(mHeight);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return mWidth + "x" + mHeight;
+    }
+
+    /** Converts this {@link SizeFCompat} into a {@link SizeF}. */
+    @RequiresApi(21)
+    @NonNull
+    public SizeF toSizeF() {
+        return Api21Impl.toSizeF(this);
+    }
+
+    /** Creates a {@link SizeFCompat} from a {@link SizeF}. */
+    @RequiresApi(21)
+    @NonNull
+    public static SizeFCompat createFromSizeF(@NonNull SizeF size) {
+        return Api21Impl.toSizeFCompat(size);
+    }
+
+    @RequiresApi(21)
+    private static final class Api21Impl {
+        @DoNotInline
+        @NonNull
+        static SizeFCompat toSizeFCompat(@NonNull SizeF size) {
+            Preconditions.checkNotNull(size);
+            return new SizeFCompat(size.getWidth(), size.getHeight());
+        }
+
+        @DoNotInline
+        @NonNull
+        static SizeF toSizeF(@NonNull SizeFCompat size) {
+            Preconditions.checkNotNull(size);
+            return new SizeF(size.getWidth(), size.getHeight());
+        }
+    }
+}
diff --git a/core/core/src/test/java/androidx/core/util/SizeFCompatTest.java b/core/core/src/test/java/androidx/core/util/SizeFCompatTest.java
new file mode 100644
index 0000000..cde04f1
--- /dev/null
+++ b/core/core/src/test/java/androidx/core/util/SizeFCompatTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2021 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.core.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.TargetApi;
+import android.util.SizeF;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(sdk = {19, 21})
+public class SizeFCompatTest {
+
+    @Test
+    public void constructor_validSize() {
+        SizeFCompat size = new SizeFCompat(100.4f, 150.2f);
+
+        assertThat(size.getWidth()).isEqualTo(100.4f);
+        assertThat(size.getHeight()).isEqualTo(150.2f);
+    }
+
+    @Test
+    public void constructor_invalidSize() {
+        assertThrows(IllegalArgumentException.class, () -> new SizeFCompat(Float.NaN, 100f));
+        assertThrows(IllegalArgumentException.class, () -> new SizeFCompat(12.5f, Float.NaN));
+    }
+
+    @Test
+    public void equals() {
+        assertThat(new SizeFCompat(100.4f, 150.2f)).isEqualTo(new SizeFCompat(100.4f, 150.2f));
+        assertThat(new SizeFCompat(100.4f, 150.2f)).isNotEqualTo(new SizeFCompat(10.4f, 150.2f));
+        assertThat(new SizeFCompat(100.4f, 150.2f)).isNotEqualTo(new SizeFCompat(100.4f, 15.2f));
+    }
+
+    @Test
+    public void hashCode_sameForEqualSizes() {
+        assertThat(new SizeFCompat(100.4f, 150.2f).hashCode())
+                .isEqualTo(new SizeFCompat(100.4f, 150.2f).hashCode());
+        assertThat(new SizeFCompat(100.4f, 150.2f).hashCode())
+                .isNotEqualTo(new SizeFCompat(10.4f, 150.2f).hashCode());
+        assertThat(new SizeFCompat(100.4f, 150.2f).hashCode())
+                .isNotEqualTo(new SizeFCompat(100.4f, 15.2f).hashCode());
+    }
+
+    @Test
+    public void toString_readable() {
+        assertThat(new SizeFCompat(10.2f, 20.4f).toString()).isEqualTo("10.2x20.4");
+    }
+
+    @TargetApi(21)
+    @Config(sdk = 21)
+    @Test
+    public void toSizeF() {
+        assertThat(new SizeFCompat(10.2f, 20.4f).toSizeF()).isEqualTo(new SizeF(10.2f, 20.4f));
+    }
+
+    @TargetApi(21)
+    @Config(sdk = 21)
+    @Test
+    public void createFromSizeF() {
+        assertThat(SizeFCompat.createFromSizeF(new SizeF(11.2f, 21.4f)))
+                .isEqualTo(new SizeFCompat(11.2f, 21.4f));
+    }
+}
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index ecc237e6..36287014 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -133,7 +133,6 @@
 \- Support for Scala 3
 For more details see .*
 Daemon will be stopped at the end of the build
-# > Task :buildSrc:build
 # > Configure project :appsearch:appsearch\-local\-backend
 Configuration on demand is an incubating feature\.
 Configuration cache is an incubating feature\.
@@ -177,9 +176,6 @@
 Note: Recompile with \-Xlint:removal for details\.
 Note\: Some input files use unchecked or unsafe operations\.
 Note\: Recompile with \-Xlint\:unchecked for details\.
-# > Task :benchmark:benchmark-junit4:processDebugAndroidTestManifest
-# > Task :benchmark:benchmark-common:processDebugAndroidTestManifest
-# > Task :tracing:tracing:compileDebugAndroidTestJavaWithJavac
 # > Task :ui:ui-tooling:processDebugAndroidTestManifest
 application@android:debuggable was tagged at .*\.xml:[0-9]+ to replace other declarations but no other declaration present
 \$OUT_DIR/androidx/wear/compose/compose\-material\-benchmark/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
@@ -208,7 +204,6 @@
 \$OUT_DIR/androidx/compose/ui/ui\-graphics/ui\-graphics\-benchmark/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :compose:foundation:foundation-layout:processDebugAndroidTestManifest
 \$SUPPORT/compose/foundation/foundation\-layout/src/androidAndroidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :compose:navigation:navigation:processDebugAndroidTestManifest
 # > Task :compose:runtime:runtime-saveable:processDebugAndroidTestManifest
 \$SUPPORT/compose/runtime/runtime\-saveable/src/androidAndroidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :compose:runtime:runtime:benchmark:processReleaseAndroidTestManifest
@@ -230,7 +225,6 @@
 \$OUT_DIR\/androidx\/docs\-public\/build\/unzippedDocsSources\/androidx\/work\/impl\/utils\/ForceStopRunnable\.java\:[0-9]+\: error\: cannot find symbol
 # > Task :buildOnServer
 [0-9]+ problems were found reusing the configuration cache, [0-9]+ of which seem unique\.
-\- Task `:docs\-tip\-of\-tree:unzipSampleSources` of type `org\.gradle\.api\.tasks\.Sync`: cannot deserialize object of type 'org\.gradle\.api\.Project' as these are not supported with the configuration cache\.
 [0-9]+ actionable tasks: [0-9]+ executed, [0-9]+ up\-to\-date
 Configuration cache entry reused with [0-9]+ problems\.
 See the profiling report at: file://\$GRADLE_USER_HOME/daemon/.*/reports/profile/profile\-[0-9]+\-[0-9]+\-[0-9]+\-[0-9]+\-[0-9]+\-[0-9]+\.html
@@ -245,19 +239,8 @@
 # > Task :lifecycle:lifecycle-common:compileJava
 Note: \$[^ ]+ uses or overrides a deprecated API\.
 Note: Recompile with \-Xlint\:deprecation for details\.
-# > Task :concurrent:concurrent-futures:compileJava
-# > Task :contentpager:contentpager:compileReleaseJavaWithJavac
-# > Task :coordinatorlayout:coordinatorlayout:compileReleaseJavaWithJavac
-# > Task :customview:customview:compileReleaseJavaWithJavac
-# > Task :recommendation:recommendation:compileReleaseJavaWithJavac
-# > Task :textclassifier:textclassifier:compileReleaseJavaWithJavac
 # > Task :publicDocsTask
 [0-9]+ warnings
-# > Task :support-animation-demos:compileDebugJavaWithJavac
-# > Task :lint-demos:lint-demo-appcompat:compileDebugJavaWithJavac
-# > Task :support-transition-demos:compileDebugJavaWithJavac
-# > Task :support-content-demos:compileDebugJavaWithJavac
-# > Task :support-preference-demos:compileDebugJavaWithJavac
 # > Task :startup:integration-tests:first-library:processDebugManifest
 \$SUPPORT/startup/integration\-tests/first\-library/src/main/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 meta\-data\#androidx\.work\.WorkManagerInitializer was tagged at AndroidManifest\.xml\:[0-9]+ to remove other declarations but no other declaration present
@@ -265,23 +248,12 @@
 Note: \$SUPPORT/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser\.java uses unchecked or unsafe operations\.
 # > Task :emoji2:emoji2-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/emoji[0-9]+/emoji[0-9]+\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :gridlayout:gridlayout:compileDebugAndroidTestJavaWithJavac
-# > Task :leanback:leanback-tab:processDebugAndroidTestManifest
-# > Task :lifecycle:lifecycle-extensions:processDebugAndroidTestManifest
 # > Task :leanback:leanback:compileDebugAndroidTestJavaWithJavac
 reason: class file for kotlin\.annotation\.AnnotationTarget not found
 reason: class file for kotlin\.annotation\.AnnotationRetention not found
 warning: unknown enum constant AnnotationTarget\.ANNOTATION_CLASS
-# > Task :navigation:navigation-dynamic-features-runtime:processDebugAndroidTestManifest
-# > Task :mediarouter:mediarouter:compileDebugAndroidTestJavaWithJavac
-# > Task :palette:palette:processDebugAndroidTestManifest
-# > Task :percentlayout:percentlayout:processDebugAndroidTestManifest
-# > Task :preference:preference-ktx:processDebugAndroidTestManifest
 # > Task :recyclerview:recyclerview-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/recyclerview/recyclerview\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :remotecallback:remotecallback:processDebugAndroidTestManifest
-# > Task :recyclerview:recyclerview-selection:compileDebugAndroidTestJavaWithJavac
-# > Task :savedstate:savedstate:processDebugAndroidTestManifest
 # > Task :room:room-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/room/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :room:room-benchmark:kaptReleaseAndroidTestKotlin
@@ -291,7 +263,6 @@
 \$SUPPORT/room/integration\-tests/noappcompattestapp/src/androidTest/java/androidx/room/integration/noappcompat/BareRelationDatabaseTest\.java:[0-9]+: warning: The return value includes a POJO with a @Relation\. It is usually desired to annotate this method with @Transaction to avoid possibility of inconsistent results between the POJO and its relations\. See https://developer\.android\.com/reference/androidx/room/Transaction\.html for details\.
 UserAndPets getUserWithPets\(long id\);
 List<UserAndPet> getUsersWithPet\(\);
-# > Task :textclassifier:integration-tests:testapp:compileDebugAndroidTestJavaWithJavac
 # > Task :room:integration-tests:room-testapp:compileDebugAndroidTestJavaWithJavac
 Note: \$SUPPORT/room/integration\-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestUtil\.java uses unchecked or unsafe operations\.
 # > Task :activity:integration-tests:testapp:processDebugAndroidTestManifest
@@ -312,16 +283,7 @@
 # > Task :compose:material:material:icons:generator:zipHtmlResultsOfTest
 Html results of .* zipped into.*\.zip
 [0-9]+ problems were found storing the configuration cache, [0-9]+ of which seem unique\.
-\- Task `:docs\-tip\-of\-tree:unzipSampleSources` of type `org\.gradle\.api\.tasks\.Sync`: cannot serialize object of type 'org\.gradle\.api\.internal\.artifacts\.configurations\.DefaultConfiguration', a subtype of 'org\.gradle\.api\.artifacts\.Configuration', as these are not supported with the configuration cache\.
 See https://docs\.gradle\.org/[0-9]+\.[0-9]+.*/userguide/configuration_cache\.html\#config_cache:requirements:disallowed_types
-\- Task `:docs\-tip\-of\-tree:unzipSampleSources` of type `org\.gradle\.api\.tasks\.Sync`: cannot deserialize object of type 'org\.gradle\.api\.artifacts\.Configuration' as these are not supported with the configuration cache\.
-\- Task `:docs\-tip\-of\-tree:unzipSampleSources` of type `org\.gradle\.api\.tasks\.Sync`: value 'file collection' is not assignable to 'org\.gradle\.api\.artifacts\.Configuration'
-\- Task `:docs\-tip\-of\-tree:unzipSourcesForDackka` of type `org\.gradle\.api\.tasks\.Sync`: cannot deserialize object of type 'org\.gradle\.api\.Project' as these are not supported with the configuration cache\.
-\- Task `:docs\-tip\-of\-tree:unzipSourcesForDackka` of type `org\.gradle\.api\.tasks\.Sync`: cannot deserialize object of type 'org\.gradle\.api\.artifacts\.Configuration' as these are not supported with the configuration cache\.
-\- Task `:docs\-tip\-of\-tree:unzipSourcesForDackka` of type `org\.gradle\.api\.tasks\.Sync`: value 'file collection' is not assignable to 'org\.gradle\.api\.artifacts\.Configuration'
-\- Task `:docs\-tip\-of\-tree:unzipDocsSources` of type `org\.gradle\.api\.tasks\.Sync`: cannot deserialize object of type 'org\.gradle\.api\.artifacts\.Configuration' as these are not supported with the configuration cache\.
-\- Task `:docs\-tip\-of\-tree:unzipDocsSources` of type `org\.gradle\.api\.tasks\.Sync`: value 'file collection' is not assignable to 'org\.gradle\.api\.artifacts\.Configuration'
-\- Task `:docs\-tip\-of\-tree:unzipSampleSources` of type `org\.gradle\.api\.tasks\.Sync`: cannot serialize object of type 'org\.gradle\.api\.internal\.project\.DefaultProject', a subtype of 'org\.gradle\.api\.Project', as these are not supported with the configuration cache\.
 [0-9]+ problem was found storing the configuration cache.
 \- Task `:listTaskOutputs` of type `androidx\.build\.ListTaskOutputsTask`: invocation of 'Task\.project' at execution time is unsupported\.
 \- Task `[^ ]*checkExternalLicenses` of type `[^ ]*CheckExternalDependencyLicensesTask`: invocation of 'Task\.project' at execution time is unsupported\.
@@ -390,7 +352,6 @@
 w\: \$SUPPORT\/viewpager[0-9]+\/viewpager[0-9]+\/src\/androidTest\/java\/androidx\/viewpager[0-9]+\/widget\/OnApplyWindowInsetsListenerTest\.kt\: \([0-9]+\, [0-9]+\)\: Unnecessary non\-null assertion \(\!\!\) on a non\-null receiver of type WindowInsetsCompat
 w\: \$SUPPORT\/viewpager[0-9]+\/viewpager[0-9]+\/src\/androidTest\/java\/androidx\/viewpager[0-9]+\/widget\/OnApplyWindowInsetsListenerTest\.kt\: \([0-9]+\, [0-9]+\)\: \'consumeStableInsets\(\)\: WindowInsetsCompat\' is deprecated\. Deprecated in Java
 w\: \$SUPPORT\/viewpager[0-9]+\/viewpager[0-9]+\/src\/androidTest\/java\/androidx\/viewpager[0-9]+\/widget\/OnApplyWindowInsetsListenerTest\.kt\: \([0-9]+\, [0-9]+\)\: \'consumeDisplayCutout\(\)\: WindowInsetsCompat\' is deprecated\. Deprecated in Java
-# > Task :contentpager:contentpager:compileDebugAndroidTestJavaWithJavac
 # > Task :docs-public:dokkaKotlinDocs
 No documentation for .*
 Found an unresolved type in androidx\.compose\.runtime\.snapshots\.SnapshotStateList\$add\(androidx\.compose\.runtime\.snapshots\.SnapshotStateList\.T\) \(SnapshotStateList\.kt:[0-9]+\)
@@ -597,39 +558,8 @@
 \$SUPPORT/compose/ui/ui\-tooling/src/androidAndroidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 No issues found.*
 dagger\.lint\.DaggerIssueRegistry in .*/lint\.jar does not specify a vendor; see IssueRegistry#vendor
-# > Task :camera:camera-camera2-pipe:reportLibraryMetrics
-Info: Stripped invalid locals information from [0-9]+ methods\.
-Info: Methods with invalid locals information:
-java\.lang\.Object androidx\.glance\.appwidget\.GlanceAppWidget\.compose\$glance_appwidget_release\(android\.content\.Context\, android\.appwidget\.AppWidgetManager\, int\, java\.lang\.Object\, android\.os\.Bundle\, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.glance\.state\.GlanceState\.getDataStore\(android\.content\.Context, androidx\.glance\.state\.GlanceStateDefinition, java\.lang\.String, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.wear\.complications\.ComplicationDataSourceInfoRetriever\.retrievePreviewComplicationData\(android\.content\.ComponentName, androidx\.wear\.complications\.data\.ComplicationType, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.wear\.watchface\.editor\.BaseEditorSession\.openComplicationDataSourceChooser\$suspendImpl\(androidx\.wear\.watchface\.editor\.BaseEditorSession, int, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.compose\.ui\.platform\.GlobalSnapshotManager\$ensureStarted\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-java\.lang\.Object androidx\.wear\.watchface\.editor\.BaseEditorSession\$fetchComplicationsData\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-java\.lang\.Object androidx\.camera\.camera[0-9]+\.pipe\.CameraDevicesKt\$find\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-java\.lang\.Object androidx\.camera\.camera[0-9]+\.pipe\.compat\.VirtualCameraManager\$requestLoop\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-Information in locals\-table is invalid with respect to the stack map table\. Local refers to non\-present stack map type for register: [0-9]+ with constraint OBJECT\.
-java\.lang\.Object androidx\.camera\.camera[0-9]+\.pipe\.compat\.VirtualCameraManager\.openCameraWithRetry\-RzXb[0-9]+QE\(java\.lang\.String, kotlinx\.coroutines\.CoroutineScope, kotlin\.coroutines\.Continuation\)
-Information in locals\-table is invalid with respect to the stack map table\. Local refers to non\-present stack map type for register: [0-9]+ with constraint INT\.
-Info: Some warnings are typically a sign of using an outdated Java toolchain\. To fix, recompile the source with an updated toolchain\.
-# > Task :paging:paging-samples:reportLibraryMetrics
-java\.lang\.Object androidx\.paging\.samples\.RemoteMediatorSampleKt\$remoteMediatorItemKeyedSample\$ExampleRemoteMediator\.load\(androidx\.paging\.LoadType, androidx\.paging\.PagingState, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.paging\.samples\.RemoteMediatorSampleKt\$remoteMediatorPageKeyedSample\$ExampleRemoteMediator\.load\(androidx\.paging\.LoadType, androidx\.paging\.PagingState, kotlin\.coroutines\.Continuation\)
-# > Task :wear:wear-complications-data:reportLibraryMetrics
-Info: Stripped invalid locals information from [0-9]+ method\.
-# > Task :wear:wear-watchface-editor:reportLibraryMetrics
-java\.lang\.Object androidx\.wear\.watchface\.editor\.BaseEditorSession\.getPreviewData\$wear_watchface_editor_release\(androidx\.wear\.complications\.ComplicationDataSourceInfoRetriever, androidx\.wear\.complications\.ComplicationDataSourceInfo, kotlin\.coroutines\.Continuation\)
-# > Task :work:work-runtime-ktx:reportLibraryMetrics
-java\.lang\.Object androidx\.work\.OperationKt\.await\(androidx\.work\.Operation, kotlin\.coroutines\.Continuation\)
-# > Task :compose:material:material:reportLibraryMetrics
-java\.lang\.Object androidx\.compose\.material\.SnackbarHostState\.showSnackbar\(java\.lang\.String, java\.lang\.String, androidx\.compose\.material\.SnackbarDuration, kotlin\.coroutines\.Continuation\)
 # > Task :compose:foundation:foundation:androidReleaseSourcesJar
 Encountered duplicate path "android[a-zA-Z]*/.+" during copy operation configured with DuplicatesStrategy\.WARN
-# > Task :camera:camera-camera2-pipe:reportLibraryMetrics
-java\.lang\.Object androidx\.camera\.camera[0-9]+\.pipe\.compat\.Camera[0-9]+DeviceCache\$getCameras\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-Type information in locals\-table is inconsistent\. Cannot constrain type: @Nullable androidx\.camera\.camera[0-9]+\.pipe\.core\.Debug \{\} for value: v[0-9]+\(this_\$iv\$iv\) by constraint INT\.
-# > Task :compose:foundation:foundation:integration-tests:foundation-demos:reportLibraryMetrics
-java\.lang\.Object androidx\.compose\.foundation\.demos\.ListDemosKt\$ListHoistedStateDemo\$[0-9]+\$[0-9]+\$[0-9]+\$[0-9]+\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
 # > Task :profileinstaller:profileinstaller-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/profileinstaller/profileinstaller\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :wear:compose:compose-material-benchmark:processReleaseAndroidTestManifest
@@ -675,3 +605,9 @@
 C/C\+\+: (debug|release)\|(x86_64|x86|arm64\-v8a|armeabi\-v7a) :Packaging for\: (amd\-[0-9]+|armhf\-[0-9]+|x86\-[0-9]+)
 C/C\+\+: (debug|release)\|(x86_64|x86|arm64\-v8a|armeabi\-v7a) :  CMakeLists\.txt:[0-9]+ \(PROJECT\)
 C/C\+\+: (debug|release)\|(x86_64|x86|arm64\-v8a|armeabi\-v7a) :Compiling for ARM
+# > Task :glance:glance:reportLibraryMetrics
+Info: Stripped invalid locals information from [0-9]+ method\.
+Info: Methods with invalid locals information:
+java\.lang\.Object androidx\.glance\.state\.GlanceState\.getDataStore\(android\.content\.Context, androidx\.glance\.state\.GlanceStateDefinition, java\.lang\.String, kotlin\.coroutines\.Continuation\)
+Information in locals\-table is invalid with respect to the stack map table\. Local refers to non\-present stack map type for register: [0-9]+ with constraint OBJECT\.
+Info: Some warnings are typically a sign of using an outdated Java toolchain\. To fix, recompile the source with an updated toolchain\.
\ No newline at end of file
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 6b5e28f..162e164 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -31,13 +31,13 @@
     docs("androidx.biometric:biometric-ktx:1.2.0-alpha04")
     samples("androidx.biometric:biometric-ktx-samples:1.2.0-alpha04")
     docs("androidx.browser:browser:1.4.0")
-    docs("androidx.camera:camera-camera2:1.1.0-alpha10")
-    docs("androidx.camera:camera-core:1.1.0-alpha10")
-    docs("androidx.camera:camera-extensions:1.0.0-alpha30")
+    docs("androidx.camera:camera-camera2:1.1.0-alpha11")
+    docs("androidx.camera:camera-core:1.1.0-alpha11")
+    docs("androidx.camera:camera-extensions:1.0.0-alpha31")
     stubs(fileTree(dir: "../camera/camera-extensions-stub", include: ["camera-extensions-stub.jar"]))
-    docs("androidx.camera:camera-lifecycle:1.1.0-alpha10")
-    docs("androidx.camera:camera-video:1.1.0-alpha10")
-    docs("androidx.camera:camera-view:1.0.0-alpha30")
+    docs("androidx.camera:camera-lifecycle:1.1.0-alpha11")
+    docs("androidx.camera:camera-video:1.1.0-alpha11")
+    docs("androidx.camera:camera-view:1.0.0-alpha31")
     docs("androidx.car.app:app:1.2.0-alpha01")
     docs("androidx.car.app:app-automotive:1.2.0-alpha01")
     docs("androidx.car.app:app-projected:1.2.0-alpha01")
@@ -92,7 +92,7 @@
     docs("androidx.concurrent:concurrent-futures:1.1.0")
     docs("androidx.concurrent:concurrent-futures-ktx:1.1.0")
     docs("androidx.contentpager:contentpager:1.0.0")
-    docs("androidx.coordinatorlayout:coordinatorlayout:1.2.0-alpha01")
+    docs("androidx.coordinatorlayout:coordinatorlayout:1.2.0-beta01")
     docs("androidx.core:core-google-shortcuts:1.1.0-alpha02")
     docs("androidx.core:core-role:1.1.0-alpha02")
     docs("androidx.core:core-animation:1.0.0-alpha02")
@@ -227,7 +227,7 @@
     docs("androidx.slice:slice-builders-ktx:1.0.0-alpha08")
     docs("androidx.slice:slice-core:1.1.0-alpha02")
     docs("androidx.slice:slice-view:1.1.0-alpha02")
-    docs("androidx.slidingpanelayout:slidingpanelayout:1.2.0-beta01")
+    docs("androidx.slidingpanelayout:slidingpanelayout:1.2.0-rc01")
     docs("androidx.sqlite:sqlite:2.2.0-beta02")
     docs("androidx.sqlite:sqlite-framework:2.2.0-beta02")
     docs("androidx.sqlite:sqlite-ktx:2.2.0-beta02")
@@ -278,13 +278,13 @@
     samples("androidx.wear:wear-input-samples:1.2.0-alpha01")
     docs("androidx.wear:wear-input-testing:1.2.0-alpha02")
     docs("androidx.webkit:webkit:1.4.0")
-    docs("androidx.window:window:1.0.0-beta03")
+    docs("androidx.window:window:1.0.0-beta04")
     stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
     stubs("androidx.window:window-extensions:1.0.0-alpha01")
-    docs("androidx.window:window-java:1.0.0-beta03")
-    docs("androidx.window:window-rxjava2:1.0.0-beta03")
-    docs("androidx.window:window-rxjava3:1.0.0-beta03")
-    docs("androidx.window:window-testing:1.0.0-beta03")
+    docs("androidx.window:window-java:1.0.0-beta04")
+    docs("androidx.window:window-rxjava2:1.0.0-beta04")
+    docs("androidx.window:window-rxjava3:1.0.0-beta04")
+    docs("androidx.window:window-testing:1.0.0-beta04")
     docs("androidx.work:work-gcm:2.7.1")
     docs("androidx.work:work-multiprocess:2.7.1")
     docs("androidx.work:work-runtime:2.7.1")
diff --git a/fragment/fragment-ktx/build.gradle b/fragment/fragment-ktx/build.gradle
index f48c186..fdc614d 100644
--- a/fragment/fragment-ktx/build.gradle
+++ b/fragment/fragment-ktx/build.gradle
@@ -39,7 +39,7 @@
         because 'Mirror fragment dependency graph for -ktx artifacts'
     }
     api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")
-    api("androidx.savedstate:savedstate-ktx:1.1.0") {
+    api(projectOrArtifact(":savedstate:savedstate-ktx")) {
         because 'Mirror fragment dependency graph for -ktx artifacts'
     }
     api(libs.kotlinStdlib)
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index eedd7b9..e273c7d 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -33,7 +33,7 @@
     api("androidx.lifecycle:lifecycle-livedata-core:2.3.1")
     api("androidx.lifecycle:lifecycle-viewmodel:2.3.1")
     api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1")
-    api("androidx.savedstate:savedstate:1.1.0")
+    api(projectOrArtifact(":savedstate:savedstate"))
     api("androidx.annotation:annotation-experimental:1.0.0")
     api(libs.kotlinStdlib)
 
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentSavedStateRegistryTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentSavedStateRegistryTest.kt
index c544c120..bfffb85 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentSavedStateRegistryTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentSavedStateRegistryTest.kt
@@ -17,16 +17,22 @@
 package androidx.fragment.app
 
 import android.os.Bundle
+import androidx.activity.result.ActivityResultRegistry
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.app.ActivityOptionsCompat
+import androidx.fragment.app.test.ViewModelActivity
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.Lifecycle.State.CREATED
 import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.ViewModelProvider
 import androidx.savedstate.SavedStateRegistry
+import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.testutils.RecreatedActivity
-import androidx.testutils.recreate
+import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -34,52 +40,68 @@
 @LargeTest
 class FragmentSavedStateRegistryTest {
 
-    @Suppress("DEPRECATION")
-    @get:Rule
-    var activityRule = androidx.test.rule.ActivityTestRule(FragmentSavedStateActivity::class.java)
-
-    private fun initializeSavedState(testFragment: Fragment = Fragment()) {
-        activityRule.runOnUiThread {
-            val fragmentManager = activityRule.activity.supportFragmentManager
-            fragmentManager.beginTransaction().add(testFragment, FRAGMENT_TAG).commitNow()
-            assertThat(fragmentManager.findFragmentByTag(FRAGMENT_TAG)).isNotNull()
-            assertThat(testFragment.lifecycle.currentState.isAtLeast(CREATED)).isTrue()
-            val registry = testFragment.savedStateRegistry
-            val savedState = registry.consumeRestoredStateForKey(CALLBACK_KEY)
-            assertThat(savedState).isNull()
-            registry.registerSavedStateProvider(CALLBACK_KEY, DefaultProvider())
-        }
+    private fun ActivityScenario<FragmentSavedStateActivity>.initializeSavedState(
+        testFragment: Fragment = Fragment()
+    ) = withActivity {
+        val fragmentManager = supportFragmentManager
+        fragmentManager.beginTransaction().add(testFragment, FRAGMENT_TAG).commitNow()
+        assertThat(fragmentManager.findFragmentByTag(FRAGMENT_TAG)).isNotNull()
+        assertThat(testFragment.lifecycle.currentState.isAtLeast(CREATED)).isTrue()
+        val registry = testFragment.savedStateRegistry
+        val savedState = registry.consumeRestoredStateForKey(CALLBACK_KEY)
+        assertThat(savedState).isNull()
+        registry.registerSavedStateProvider(CALLBACK_KEY, DefaultProvider())
     }
 
     @Test
     fun savedState() {
-        initializeSavedState()
-        val recreated = activityRule.recreate()
-        activityRule.runOnUiThread {
-            assertThat(recreated.fragment().lifecycle.currentState.isAtLeast(CREATED)).isTrue()
-            checkDefaultSavedState(recreated.fragment().savedStateRegistry)
+        with(ActivityScenario.launch(FragmentSavedStateActivity::class.java)) {
+            initializeSavedState()
+            recreate()
+            withActivity {
+                assertThat(fragment.lifecycle.currentState.isAtLeast(CREATED)).isTrue()
+                checkDefaultSavedState(fragment.savedStateRegistry)
+            }
         }
     }
 
     @Test
     fun savedStateLateInit() {
-        initializeSavedState()
-        val recreated = activityRule.recreate()
-        activityRule.runOnUiThread {
-            recreated.fragment().lifecycle.addObserver(
-                LifecycleEventObserver { _, event ->
-                    if (event == Lifecycle.Event.ON_RESUME) {
-                        checkDefaultSavedState(recreated.fragment().savedStateRegistry)
+        with(ActivityScenario.launch(FragmentSavedStateActivity::class.java)) {
+            initializeSavedState()
+            recreate()
+            withActivity {
+                fragment.lifecycle.addObserver(
+                    LifecycleEventObserver { _, event ->
+                        if (event == Lifecycle.Event.ON_RESUME) {
+                            checkDefaultSavedState(fragment.savedStateRegistry)
+                        }
                     }
-                }
-            )
+                )
+            }
         }
     }
 
     @Test
     fun savedStateEarlyRegister() {
-        initializeSavedState(OnCreateCheckingFragment())
-        activityRule.recreate()
+        with(ActivityScenario.launch(FragmentSavedStateActivity::class.java)) {
+            initializeSavedState(OnCreateCheckingFragment())
+            recreate()
+        }
+    }
+
+    @Test
+    fun savedStateOnActivityResult() {
+        with(ActivityScenario.launch(FragmentSavedStateActivity::class.java)) {
+            val registry = withActivity { registry }
+            initializeSavedState(OnActivityResultCheckingFragment(registry))
+            recreate()
+            moveToState(Lifecycle.State.CREATED)
+            withActivity {
+                (fragment as OnActivityResultCheckingFragment).launcher.launch("")
+            }
+            moveToState(Lifecycle.State.RESUMED)
+        }
     }
 }
 
@@ -90,7 +112,32 @@
 }
 
 class FragmentSavedStateActivity : RecreatedActivity() {
-    fun fragment() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG)
+    val registry = object : ActivityResultRegistry() {
+        override fun <I : Any?, O : Any?> onLaunch(
+            requestCode: Int,
+            contract: ActivityResultContract<I, O>,
+            input: I,
+            options: ActivityOptionsCompat?
+        ) {
+            if (contract is ActivityResultContracts.GetContent) {
+                dispatchResult(requestCode, null)
+            }
+        }
+    }
+
+    init {
+        supportFragmentManager.fragmentFactory = object : FragmentFactory() {
+            override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
+                return when (loadFragmentClass(classLoader, className)) {
+                    OnActivityResultCheckingFragment::class.java ->
+                        OnActivityResultCheckingFragment(registry)
+                    else -> super.instantiate(classLoader, className)
+                }
+            }
+        }
+    }
+
+    val fragment: Fragment get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG)
         ?: throw IllegalStateException("Fragment under test wasn't found")
 }
 
@@ -103,6 +150,23 @@
     }
 }
 
+class OnActivityResultCheckingFragment(
+    activityResultRegistry: ActivityResultRegistry
+) : Fragment() {
+    val viewModel by lazy {
+        ViewModelProvider(this).get(ViewModelActivity.TestSavedStateViewModel::class.java)
+    }
+
+    val launcher = registerForActivityResult(
+        ActivityResultContracts.GetContent(),
+        activityResultRegistry
+    ) {
+        // Accessing the ViewModel is what triggers the startup behavior of
+        // SavedStateRegistryController
+        viewModel
+    }
+}
+
 private class DefaultProvider : SavedStateRegistry.SavedStateProvider {
     override fun saveState() = Bundle().apply { putString(KEY, VALUE) }
 }
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index 8dc962e..8fcec63 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -572,6 +572,12 @@
         // The default factory depends on the SavedStateRegistry so it
         // needs to be reset when the SavedStateRegistry is reset
         mDefaultFactory = null;
+        registerOnPreAttachListener(new OnPreAttachedListener() {
+            @Override
+            void onPreAttached() {
+                mSavedStateRegistryController.performAttach();
+            }
+        });
     }
 
     /**
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index b3a2824..2efcafe 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -35,9 +35,7 @@
 
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
-    method public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.String> getLocalUiKey();
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> LocalAppWidgetOptions;
-    property public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.String> LocalUiKey;
   }
 
   public final class CornerRadiusKt {
@@ -142,7 +140,7 @@
   }
 
   public final class LaunchActivityIntentActionKt {
-    method public static androidx.glance.action.Action actionLaunchActivity(android.content.Intent intent);
+    method public static androidx.glance.action.Action actionLaunchActivity(android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters);
   }
 
   public final class RunCallbackActionKt {
@@ -181,6 +179,15 @@
 
 }
 
+package androidx.glance.appwidget.state {
+
+  public final class GlanceAppWidgetStateKt {
+    method public static suspend <T> Object? getAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T> p);
+    method public static suspend <T> Object? updateAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T> p);
+  }
+
+}
+
 package androidx.glance.appwidget.translators {
 
   public final class CheckBoxTranslatorKt {
diff --git a/glance/glance-appwidget/api/public_plus_experimental_current.txt b/glance/glance-appwidget/api/public_plus_experimental_current.txt
index b3a2824..2efcafe 100644
--- a/glance/glance-appwidget/api/public_plus_experimental_current.txt
+++ b/glance/glance-appwidget/api/public_plus_experimental_current.txt
@@ -35,9 +35,7 @@
 
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
-    method public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.String> getLocalUiKey();
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> LocalAppWidgetOptions;
-    property public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.String> LocalUiKey;
   }
 
   public final class CornerRadiusKt {
@@ -142,7 +140,7 @@
   }
 
   public final class LaunchActivityIntentActionKt {
-    method public static androidx.glance.action.Action actionLaunchActivity(android.content.Intent intent);
+    method public static androidx.glance.action.Action actionLaunchActivity(android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters);
   }
 
   public final class RunCallbackActionKt {
@@ -181,6 +179,15 @@
 
 }
 
+package androidx.glance.appwidget.state {
+
+  public final class GlanceAppWidgetStateKt {
+    method public static suspend <T> Object? getAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T> p);
+    method public static suspend <T> Object? updateAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T> p);
+  }
+
+}
+
 package androidx.glance.appwidget.translators {
 
   public final class CheckBoxTranslatorKt {
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index b3a2824..2efcafe 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -35,9 +35,7 @@
 
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
-    method public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.String> getLocalUiKey();
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> LocalAppWidgetOptions;
-    property public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.String> LocalUiKey;
   }
 
   public final class CornerRadiusKt {
@@ -142,7 +140,7 @@
   }
 
   public final class LaunchActivityIntentActionKt {
-    method public static androidx.glance.action.Action actionLaunchActivity(android.content.Intent intent);
+    method public static androidx.glance.action.Action actionLaunchActivity(android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters);
   }
 
   public final class RunCallbackActionKt {
@@ -181,6 +179,15 @@
 
 }
 
+package androidx.glance.appwidget.state {
+
+  public final class GlanceAppWidgetStateKt {
+    method public static suspend <T> Object? getAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T> p);
+    method public static suspend <T> Object? updateAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T> p);
+  }
+
+}
+
 package androidx.glance.appwidget.translators {
 
   public final class CheckBoxTranslatorKt {
diff --git a/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/LayoutGenerator.kt b/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/LayoutGenerator.kt
index 6a82308..74ce99f 100644
--- a/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/LayoutGenerator.kt
+++ b/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/LayoutGenerator.kt
@@ -323,6 +323,7 @@
                 setNamedItemNS(androidId("@id/rootView"))
                 setNamedItemNS(androidWidth(width))
                 setNamedItemNS(androidHeight(height))
+                setNamedItemNS(androidAttr("theme", "@style/Glance.AppWidget.Theme"))
             }
             val stub = createElement("ViewStub")
             root.appendChild(stub)
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ActionAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ActionAppWidget.kt
index fd7fcd2..c661d49 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ActionAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ActionAppWidget.kt
@@ -23,36 +23,43 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
 import androidx.glance.Button
 import androidx.glance.GlanceId
 import androidx.glance.GlanceModifier
 import androidx.glance.Image
 import androidx.glance.ImageProvider
 import androidx.glance.LocalContext
-import androidx.glance.appwidget.action.ActionCallback
 import androidx.glance.action.ActionParameters
 import androidx.glance.action.actionLaunchActivity
 import androidx.glance.action.actionParametersOf
-import androidx.glance.appwidget.action.actionRunCallback
 import androidx.glance.action.clickable
 import androidx.glance.appwidget.GlanceAppWidget
 import androidx.glance.appwidget.GlanceAppWidgetReceiver
+import androidx.glance.appwidget.action.ActionCallback
 import androidx.glance.appwidget.action.actionLaunchActivity
+import androidx.glance.appwidget.action.actionRunCallback
 import androidx.glance.appwidget.appWidgetBackground
 import androidx.glance.appwidget.cornerRadius
+import androidx.glance.appwidget.state.updateAppWidgetState
+import androidx.glance.currentState
 import androidx.glance.layout.Alignment
 import androidx.glance.layout.Column
 import androidx.glance.layout.fillMaxSize
 import androidx.glance.layout.padding
 import androidx.glance.layout.size
+import androidx.glance.state.PreferencesGlanceStateDefinition
+import androidx.glance.action.toParametersKey
 import androidx.glance.text.FontWeight
 import androidx.glance.text.Text
 import androidx.glance.text.TextDecoration
 import androidx.glance.text.TextStyle
-import java.util.concurrent.atomic.AtomicBoolean
 
 class ActionAppWidget : GlanceAppWidget() {
 
+    override val stateDefinition = PreferencesGlanceStateDefinition
+
     @Composable
     override fun Content() {
         Column(
@@ -61,11 +68,12 @@
             verticalAlignment = Alignment.Vertical.CenterVertically,
             horizontalAlignment = Alignment.Horizontal.CenterHorizontally
         ) {
+            val shouldChangeView = currentState<Preferences>()[shouldChangeViewKey] ?: false
             Text(
                 text = "Tap to change me",
                 style = TextStyle(
                     fontSize = 16.sp, fontWeight = FontWeight.Bold,
-                    textDecoration = if (shouldChangeView.get()) {
+                    textDecoration = if (shouldChangeView) {
                         TextDecoration.Underline
                     } else {
                         TextDecoration.None
@@ -76,12 +84,12 @@
                     .clickable(
                         actionRunCallback<UpdateAction>(
                             actionParametersOf(
-                                KEY_USE_UNDERLINE to shouldChangeView.get()
+                                shouldChangeViewKey.toParametersKey() to shouldChangeView
                             )
                         )
                     )
             )
-            if (shouldChangeView.get()) {
+            if (shouldChangeView) {
                 Image(
                     ImageProvider(R.drawable.compose),
                     "Compose",
@@ -108,15 +116,19 @@
     }
 }
 
-private val KEY_USE_UNDERLINE = ActionParameters.Key<Boolean>("underline")
-private var shouldChangeView = AtomicBoolean(false)
+private val shouldChangeViewKey = booleanPreferencesKey("shouldChangeView")
 
 /**
- * Action to update the [shouldChangeView] value whenever users clicks on text
+ * Action to update the [shouldChangeViewKey] value whenever users clicks on text
  */
 class UpdateAction : ActionCallback {
     override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
-        shouldChangeView.set(!parameters.getOrDefault(KEY_USE_UNDERLINE, false))
+        updateAppWidgetState(context, PreferencesGlanceStateDefinition, glanceId) { prefs ->
+            prefs.toMutablePreferences().apply {
+                this[shouldChangeViewKey] =
+                    !(parameters[shouldChangeViewKey.toParametersKey()] ?: false)
+            }
+        }
         ActionAppWidget().update(context, glanceId)
     }
 }
@@ -128,4 +140,4 @@
 
 class ActionAppWidgetReceiver : GlanceAppWidgetReceiver() {
     override val glanceAppWidget: GlanceAppWidget = ActionAppWidget()
-}
\ No newline at end of file
+}
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt
index fdb499c..2b88886 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt
@@ -21,30 +21,25 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
-import androidx.datastore.core.DataStore
-import androidx.datastore.preferences.core.PreferenceDataStoreFactory
 import androidx.datastore.preferences.core.Preferences
-import androidx.datastore.preferences.core.edit
 import androidx.datastore.preferences.core.intPreferencesKey
-import androidx.datastore.preferences.preferencesDataStoreFile
 import androidx.glance.Button
 import androidx.glance.GlanceId
 import androidx.glance.GlanceModifier
 import androidx.glance.LocalSize
 import androidx.glance.action.ActionParameters
-import androidx.glance.appwidget.action.ActionCallback
-import androidx.glance.appwidget.action.actionRunCallback
-import androidx.glance.action.actionParametersOf
 import androidx.glance.appwidget.CheckBox
 import androidx.glance.appwidget.CheckBoxColors
 import androidx.glance.appwidget.GlanceAppWidget
 import androidx.glance.appwidget.GlanceAppWidgetReceiver
-import androidx.glance.appwidget.LocalUiKey
 import androidx.glance.appwidget.SizeMode
 import androidx.glance.appwidget.Switch
 import androidx.glance.appwidget.SwitchColors
+import androidx.glance.appwidget.action.ActionCallback
+import androidx.glance.appwidget.action.actionRunCallback
 import androidx.glance.appwidget.appWidgetBackground
 import androidx.glance.appwidget.cornerRadius
+import androidx.glance.appwidget.state.updateAppWidgetState
 import androidx.glance.appwidget.unit.ColorProvider
 import androidx.glance.background
 import androidx.glance.currentState
@@ -54,18 +49,17 @@
 import androidx.glance.layout.fillMaxSize
 import androidx.glance.layout.fillMaxWidth
 import androidx.glance.layout.padding
-import androidx.glance.state.GlanceState
 import androidx.glance.state.GlanceStateDefinition
+import androidx.glance.state.PreferencesGlanceStateDefinition
 import androidx.glance.text.FontStyle
 import androidx.glance.text.FontWeight
 import androidx.glance.text.Text
 import androidx.glance.text.TextStyle
-import java.io.File
 
 class CompoundButtonAppWidget : GlanceAppWidget() {
 
     override val sizeMode: SizeMode = SizeMode.Exact
-    override var stateDefinition: GlanceStateDefinition<*> = MyStateDefinition
+    override var stateDefinition: GlanceStateDefinition<*> = PreferencesGlanceStateDefinition
 
     @Composable
     override fun Content() {
@@ -126,13 +120,11 @@
 @Composable
 fun CountClicks() {
     val prefs = currentState<Preferences>()
-    val count = prefs[countClicksKey]
-
-    val parameters = actionParametersOf(uiNameKey to LocalUiKey.current)
+    val count = prefs[countClicksKey] ?: 0
     Row(modifier = GlanceModifier.fillMaxWidth()) {
         Button(
             text = "Count clicks",
-            onClick = actionRunCallback<ClickAction>(parameters)
+            onClick = actionRunCallback<ClickAction>()
         )
         Text(text = "$count clicks")
     }
@@ -140,36 +132,16 @@
 
 class ClickAction : ActionCallback {
     override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
-        val uiKey = requireNotNull(parameters[uiNameKey]) {
-            "Add UI name to parameters, to access the view state."
-        }
-        GlanceState.updateValue(context, MyStateDefinition, uiKey) { prefs ->
+        updateAppWidgetState(context, PreferencesGlanceStateDefinition, glanceId) { prefs ->
             prefs.toMutablePreferences().apply {
-                this[countClicksKey] = prefs[countClicksKey]!! + 1
-            }.toPreferences()
+                this[countClicksKey] = (prefs[countClicksKey] ?: 0) + 1
+            }
         }
         CompoundButtonAppWidget().update(context, glanceId)
     }
 }
 
-object MyStateDefinition : GlanceStateDefinition<Preferences> {
-    override fun getLocation(context: Context, fileKey: String): File =
-        context.preferencesDataStoreFile(fileKey)
-
-    @Suppress("UNCHECKED_CAST")
-    override suspend fun <T> getDataStore(context: Context, fileKey: String): DataStore<T> =
-        PreferenceDataStoreFactory.create { context.preferencesDataStoreFile(fileKey) }
-            .apply {
-                edit { prefs ->
-                    if (prefs[countClicksKey] == null) {
-                        prefs[countClicksKey] = 0
-                    }
-                }
-            } as DataStore<T>
-}
-
 private val countClicksKey = intPreferencesKey("CountClicks")
-private val uiNameKey = ActionParameters.Key<String>("UiKey")
 
 class CompoundButtonAppWidgetReceiver : GlanceAppWidgetReceiver() {
     override val glanceAppWidget = CompoundButtonAppWidget()
diff --git a/glance/glance-appwidget/src/androidAndroidTest/res/values-v29/themes.xml b/glance/glance-appwidget/src/androidAndroidTest/res/values-v29/themes.xml
new file mode 100644
index 0000000..1a85e18
--- /dev/null
+++ b/glance/glance-appwidget/src/androidAndroidTest/res/values-v29/themes.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <style name="Glance.AppWidget.Theme" parent=""/>
+</resources>
diff --git a/glance/glance-appwidget/src/androidAndroidTest/res/values/themes.xml b/glance/glance-appwidget/src/androidAndroidTest/res/values/themes.xml
index efd9192..2f50199 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/res/values/themes.xml
+++ b/glance/glance-appwidget/src/androidAndroidTest/res/values/themes.xml
@@ -18,4 +18,5 @@
     <style name="Theme.TestDimensionAttr" parent="">
         <item name="dimensionAttr">@dimen/testDimension</item>
     </style>
+    <style name="Glance.AppWidget.Theme" parent=""/>
 </resources>
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ApplyModifiers.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ApplyModifiers.kt
index cbb494c..ff7c9e6 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ApplyModifiers.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/ApplyModifiers.kt
@@ -32,6 +32,7 @@
 import androidx.annotation.IdRes
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.graphics.toArgb
+import androidx.core.os.bundleOf
 import androidx.core.widget.RemoteViewsCompat.setTextViewHeight
 import androidx.core.widget.RemoteViewsCompat.setTextViewWidth
 import androidx.core.widget.RemoteViewsCompat.setViewBackgroundColor
@@ -127,6 +128,10 @@
                 is LaunchActivityIntentAction -> action.intent
                 else -> error("Action type not defined in app widget package: $action")
             }
+            val parametersPairs = action.parameters.asMap().map { (key, value) ->
+                key.name to value
+            }.toTypedArray()
+            activityIntent.putExtras(bundleOf(*parametersPairs))
 
             if (translationContext.isLazyCollectionDescendant) {
                 val fillIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CompositionLocals.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CompositionLocals.kt
index 863af1c..d812e74 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CompositionLocals.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CompositionLocals.kt
@@ -19,7 +19,6 @@
 import android.os.Bundle
 import androidx.compose.runtime.ProvidableCompositionLocal
 import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.glance.state.GlanceStateDefinition
 
 /**
  * Option Bundle accessible when generating an App Widget.
@@ -28,10 +27,3 @@
  */
 public val LocalAppWidgetOptions: ProvidableCompositionLocal<Bundle> =
     staticCompositionLocalOf { error("No default app widget options") }
-
-/**
- * Uniquely identifying key for this remote UI, for use with [GlanceStateDefinition].
- */
-// TODO: Look into refactoring the state to not need this key constant
-public val LocalUiKey =
-    staticCompositionLocalOf<String> { error("No default UI key") }
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
index d49d35f..56e7a11 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
@@ -348,13 +348,11 @@
         val recomposer = Recomposer(coroutineContext)
         val composition = Composition(applier, recomposer)
         val glanceId = AppWidgetId(appWidgetId)
-        val uiKey = createUniqueRemoteUiName(appWidgetId)
         composition.setContent {
             CompositionLocalProvider(
                 LocalContext provides context,
                 LocalGlanceId provides glanceId,
                 LocalAppWidgetOptions provides options,
-                LocalUiKey provides uiKey,
                 LocalState provides state,
                 LocalSize provides size,
             ) { Content() }
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/WidgetLayout.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/WidgetLayout.kt
index e3a38e6..71785ab 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/WidgetLayout.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/WidgetLayout.kt
@@ -280,11 +280,13 @@
     override fun getLocation(context: Context, fileKey: String): File =
         context.dataStoreFile(fileKey)
 
-    @Suppress("UNCHECKED_CAST")
-    override suspend fun <T> getDataStore(context: Context, fileKey: String): DataStore<T> =
+    override suspend fun getDataStore(
+        context: Context,
+        fileKey: String,
+    ): DataStore<LayoutProto.LayoutConfig> =
         DataStoreFactory.create(serializer = LayoutProtoSerializer) {
             context.dataStoreFile(fileKey)
-        } as DataStore<T>
+        }
 }
 
 private fun Alignment.Vertical.toProto() = when (this) {
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/action/LaunchActivityIntentAction.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/action/LaunchActivityIntentAction.kt
index 5ce93e9..7e7f48a 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/action/LaunchActivityIntentAction.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/action/LaunchActivityIntentAction.kt
@@ -19,9 +19,14 @@
 import android.app.Activity
 import android.content.Intent
 import androidx.glance.action.Action
+import androidx.glance.action.ActionParameters
 import androidx.glance.action.LaunchActivityAction
+import androidx.glance.action.actionParametersOf
 
-internal class LaunchActivityIntentAction(val intent: Intent) : LaunchActivityAction
+internal class LaunchActivityIntentAction(
+    val intent: Intent,
+    override val parameters: ActionParameters = actionParametersOf()
+) : LaunchActivityAction
 
 /**
  * Creates an [Action] that launches an [Activity] from the given [Intent] when triggered. The
@@ -30,5 +35,10 @@
  * This action is supported by app widgets only.
  *
  * @param intent the intent used to launch the activity
+ * @param parameters the parameters associated with the action. Parameter values will be added to
+ * the activity intent, keyed by the parameter key name string.
  */
-public fun actionLaunchActivity(intent: Intent): Action = LaunchActivityIntentAction(intent)
+public fun actionLaunchActivity(
+    intent: Intent,
+    parameters: ActionParameters = actionParametersOf()
+): Action = LaunchActivityIntentAction(intent, parameters)
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/state/GlanceAppWidgetState.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/state/GlanceAppWidgetState.kt
new file mode 100644
index 0000000..fc8ed71
--- /dev/null
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/state/GlanceAppWidgetState.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 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.glance.appwidget.state
+
+import android.content.Context
+import androidx.glance.GlanceId
+import androidx.glance.appwidget.AppWidgetId
+import androidx.glance.appwidget.createUniqueRemoteUiName
+import androidx.glance.state.GlanceState
+import androidx.glance.state.GlanceStateDefinition
+
+/**
+ * Retrieve the state of an app widget.
+ *
+ * The state definition must be the one used for that particular app widget.
+ */
+public suspend fun <T> getAppWidgetState(
+    context: Context,
+    definition: GlanceStateDefinition<T>,
+    glanceId: GlanceId,
+): T {
+    require(glanceId is AppWidgetId) { "The glance ID is not the one of an App Widget" }
+    return GlanceState.getValue(context, definition, createUniqueRemoteUiName(glanceId.appWidgetId))
+}
+
+/**
+ * Update the state of an app widget.
+ *
+ * The state definition must be the one used for that particular app widget.
+ */
+public suspend fun <T> updateAppWidgetState(
+    context: Context,
+    definition: GlanceStateDefinition<T>,
+    glanceId: GlanceId,
+    updateState: suspend (T) -> T,
+): T {
+    require(glanceId is AppWidgetId) { "The glance ID is not the one of an App Widget" }
+    return GlanceState.updateValue(
+        context,
+        definition,
+        createUniqueRemoteUiName(glanceId.appWidgetId),
+        updateState,
+    )
+}
diff --git a/glance/glance-appwidget/src/main/res/values-v29/themes.xml b/glance/glance-appwidget/src/main/res/values-v29/themes.xml
new file mode 100644
index 0000000..4cce739
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/values-v29/themes.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <style name="Glance.AppWidget.Theme" parent="android:Theme.DeviceDefault.DayNight" />
+</resources>
diff --git a/glance/glance-appwidget/src/main/res/values/themes.xml b/glance/glance-appwidget/src/main/res/values/themes.xml
new file mode 100644
index 0000000..efd5acd
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/values/themes.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <style name="Glance.AppWidget.Theme" parent="android:Theme.DeviceDefault"/>
+</resources>
diff --git a/glance/glance-wear/api/current.txt b/glance/glance-wear/api/current.txt
index d1bb2da..66d8ba9 100644
--- a/glance/glance-wear/api/current.txt
+++ b/glance/glance-wear/api/current.txt
@@ -10,17 +10,48 @@
     method public static androidx.glance.GlanceModifier border(androidx.glance.GlanceModifier, @DimenRes int width, androidx.glance.unit.ColorProvider color);
   }
 
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.wear.TimeInterval> getLocalTimeInterval();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.wear.TimeInterval> LocalTimeInterval;
+  }
+
   public abstract class GlanceTileService extends androidx.wear.tiles.TileService {
     ctor public GlanceTileService();
     method @androidx.compose.runtime.Composable public abstract void Content();
+    method public androidx.glance.wear.TimelineMode getTimelineMode();
     method protected final com.google.common.util.concurrent.ListenableFuture<androidx.wear.tiles.ResourceBuilders.Resources> onResourcesRequest(androidx.wear.tiles.RequestBuilders.ResourcesRequest requestParams);
     method public void onStart(android.content.Intent? intent, int startId);
     method protected final com.google.common.util.concurrent.ListenableFuture<androidx.wear.tiles.TileBuilders.Tile> onTileRequest(androidx.wear.tiles.RequestBuilders.TileRequest requestParams);
+    property public androidx.glance.wear.TimelineMode timelineMode;
   }
 
   public final class NormalizeCompositionTreeKt {
   }
 
+  public final class TimeInterval {
+    ctor public TimeInterval(optional java.time.Instant start, optional java.time.Instant end);
+    method public java.time.Instant component1();
+    method public java.time.Instant component2();
+    method public androidx.glance.wear.TimeInterval copy(java.time.Instant start, java.time.Instant end);
+    method public java.time.Instant getEnd();
+    method public java.time.Instant getStart();
+    property public final java.time.Instant end;
+    property public final java.time.Instant start;
+  }
+
+  public sealed interface TimelineMode {
+  }
+
+  public static final class TimelineMode.SingleEntry implements androidx.glance.wear.TimelineMode {
+    field public static final androidx.glance.wear.TimelineMode.SingleEntry INSTANCE;
+  }
+
+  public static final class TimelineMode.TimeBoundEntries implements androidx.glance.wear.TimelineMode {
+    ctor public TimelineMode.TimeBoundEntries(java.util.Set<androidx.glance.wear.TimeInterval> timeIntervals);
+    method public java.util.Set<androidx.glance.wear.TimeInterval> getTimeIntervals();
+    property public final java.util.Set<androidx.glance.wear.TimeInterval> timeIntervals;
+  }
+
   public final class WearCompositionTranslatorKt {
   }
 
diff --git a/glance/glance-wear/api/public_plus_experimental_current.txt b/glance/glance-wear/api/public_plus_experimental_current.txt
index d1bb2da..66d8ba9 100644
--- a/glance/glance-wear/api/public_plus_experimental_current.txt
+++ b/glance/glance-wear/api/public_plus_experimental_current.txt
@@ -10,17 +10,48 @@
     method public static androidx.glance.GlanceModifier border(androidx.glance.GlanceModifier, @DimenRes int width, androidx.glance.unit.ColorProvider color);
   }
 
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.wear.TimeInterval> getLocalTimeInterval();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.wear.TimeInterval> LocalTimeInterval;
+  }
+
   public abstract class GlanceTileService extends androidx.wear.tiles.TileService {
     ctor public GlanceTileService();
     method @androidx.compose.runtime.Composable public abstract void Content();
+    method public androidx.glance.wear.TimelineMode getTimelineMode();
     method protected final com.google.common.util.concurrent.ListenableFuture<androidx.wear.tiles.ResourceBuilders.Resources> onResourcesRequest(androidx.wear.tiles.RequestBuilders.ResourcesRequest requestParams);
     method public void onStart(android.content.Intent? intent, int startId);
     method protected final com.google.common.util.concurrent.ListenableFuture<androidx.wear.tiles.TileBuilders.Tile> onTileRequest(androidx.wear.tiles.RequestBuilders.TileRequest requestParams);
+    property public androidx.glance.wear.TimelineMode timelineMode;
   }
 
   public final class NormalizeCompositionTreeKt {
   }
 
+  public final class TimeInterval {
+    ctor public TimeInterval(optional java.time.Instant start, optional java.time.Instant end);
+    method public java.time.Instant component1();
+    method public java.time.Instant component2();
+    method public androidx.glance.wear.TimeInterval copy(java.time.Instant start, java.time.Instant end);
+    method public java.time.Instant getEnd();
+    method public java.time.Instant getStart();
+    property public final java.time.Instant end;
+    property public final java.time.Instant start;
+  }
+
+  public sealed interface TimelineMode {
+  }
+
+  public static final class TimelineMode.SingleEntry implements androidx.glance.wear.TimelineMode {
+    field public static final androidx.glance.wear.TimelineMode.SingleEntry INSTANCE;
+  }
+
+  public static final class TimelineMode.TimeBoundEntries implements androidx.glance.wear.TimelineMode {
+    ctor public TimelineMode.TimeBoundEntries(java.util.Set<androidx.glance.wear.TimeInterval> timeIntervals);
+    method public java.util.Set<androidx.glance.wear.TimeInterval> getTimeIntervals();
+    property public final java.util.Set<androidx.glance.wear.TimeInterval> timeIntervals;
+  }
+
   public final class WearCompositionTranslatorKt {
   }
 
diff --git a/glance/glance-wear/api/restricted_current.txt b/glance/glance-wear/api/restricted_current.txt
index d1bb2da..66d8ba9 100644
--- a/glance/glance-wear/api/restricted_current.txt
+++ b/glance/glance-wear/api/restricted_current.txt
@@ -10,17 +10,48 @@
     method public static androidx.glance.GlanceModifier border(androidx.glance.GlanceModifier, @DimenRes int width, androidx.glance.unit.ColorProvider color);
   }
 
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.wear.TimeInterval> getLocalTimeInterval();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.wear.TimeInterval> LocalTimeInterval;
+  }
+
   public abstract class GlanceTileService extends androidx.wear.tiles.TileService {
     ctor public GlanceTileService();
     method @androidx.compose.runtime.Composable public abstract void Content();
+    method public androidx.glance.wear.TimelineMode getTimelineMode();
     method protected final com.google.common.util.concurrent.ListenableFuture<androidx.wear.tiles.ResourceBuilders.Resources> onResourcesRequest(androidx.wear.tiles.RequestBuilders.ResourcesRequest requestParams);
     method public void onStart(android.content.Intent? intent, int startId);
     method protected final com.google.common.util.concurrent.ListenableFuture<androidx.wear.tiles.TileBuilders.Tile> onTileRequest(androidx.wear.tiles.RequestBuilders.TileRequest requestParams);
+    property public androidx.glance.wear.TimelineMode timelineMode;
   }
 
   public final class NormalizeCompositionTreeKt {
   }
 
+  public final class TimeInterval {
+    ctor public TimeInterval(optional java.time.Instant start, optional java.time.Instant end);
+    method public java.time.Instant component1();
+    method public java.time.Instant component2();
+    method public androidx.glance.wear.TimeInterval copy(java.time.Instant start, java.time.Instant end);
+    method public java.time.Instant getEnd();
+    method public java.time.Instant getStart();
+    property public final java.time.Instant end;
+    property public final java.time.Instant start;
+  }
+
+  public sealed interface TimelineMode {
+  }
+
+  public static final class TimelineMode.SingleEntry implements androidx.glance.wear.TimelineMode {
+    field public static final androidx.glance.wear.TimelineMode.SingleEntry INSTANCE;
+  }
+
+  public static final class TimelineMode.TimeBoundEntries implements androidx.glance.wear.TimelineMode {
+    ctor public TimelineMode.TimeBoundEntries(java.util.Set<androidx.glance.wear.TimeInterval> timeIntervals);
+    method public java.util.Set<androidx.glance.wear.TimeInterval> getTimeIntervals();
+    property public final java.util.Set<androidx.glance.wear.TimeInterval> timeIntervals;
+  }
+
   public final class WearCompositionTranslatorKt {
   }
 
diff --git a/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/CompositionLocals.kt b/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/CompositionLocals.kt
new file mode 100644
index 0000000..99ca2cb
--- /dev/null
+++ b/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/CompositionLocals.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 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.glance.wear
+
+import androidx.compose.runtime.staticCompositionLocalOf
+
+/**
+ * Time interval of the glance tile UI being generated.
+ */
+public val LocalTimeInterval =
+    staticCompositionLocalOf<TimeInterval?> { error("No default time interval") }
\ No newline at end of file
diff --git a/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/GlanceTileService.kt b/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/GlanceTileService.kt
index 07d72f0..cf6e35a 100644
--- a/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/GlanceTileService.kt
+++ b/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/GlanceTileService.kt
@@ -22,6 +22,7 @@
 import androidx.compose.runtime.BroadcastFrameClock
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composition
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Recomposer
 import androidx.glance.Applier
 import androidx.glance.layout.EmittableBox
@@ -96,25 +97,37 @@
         super.onStart(intent, startId)
     }
 
-    private suspend fun runComposition(): CompositionResult = coroutineScope {
-        val root = EmittableBox()
-        val applier = Applier(root)
-        val recomposer = Recomposer(currentCoroutineContext())
-        val composition = Composition(applier, recomposer)
+    private suspend fun runComposition(
+        timeInterval: TimeInterval? = null
+    ): CompositionResult =
+        coroutineScope {
+            val root = EmittableBox()
+            val applier = Applier(root)
+            val recomposer = Recomposer(currentCoroutineContext())
+            val composition = Composition(applier, recomposer)
 
-        composition.setContent { Content() }
+            composition.setContent {
+                CompositionLocalProvider(
+                    LocalTimeInterval provides timeInterval
+                ) { Content() }
+            }
 
-        launch { recomposer.runRecomposeAndApplyChanges() }
+            launch { recomposer.runRecomposeAndApplyChanges() }
 
-        recomposer.close()
-        recomposer.join()
+            recomposer.close()
+            recomposer.join()
 
-        normalizeCompositionTree(this@GlanceTileService, root)
+            normalizeCompositionTree(this@GlanceTileService, root)
 
-        translateTopLevelComposition(this@GlanceTileService, root)
-    }
+            translateTopLevelComposition(this@GlanceTileService, root)
+        }
 
-    /** Implement with the layout to use in your Tile. */
+    /**
+     * Defines the handling of timeline
+     */
+    public open val timelineMode: TimelineMode = TimelineMode.SingleEntry
+
+    /** Override this method to set the layout to use in your Tile. */
     @Composable
     public abstract fun Content()
 
@@ -127,21 +140,46 @@
     final override fun onTileRequest(
         requestParams: RequestBuilders.TileRequest
     ): ListenableFuture<TileBuilders.Tile> = coroutineScope.future {
-        val content = runComposition().layout
+        val timelineBuilders = TimelineBuilders.Timeline.Builder()
+        if (timelineMode === TimelineMode.SingleEntry) {
+            val content = runComposition().layout
+
+            timelineBuilders
+                .addTimelineEntry(
+                    TimelineBuilders.TimelineEntry.Builder()
+                        .setLayout(
+                            LayoutElementBuilders.Layout.Builder()
+                                .setRoot(content)
+                                .build()
+                        ).build()
+                )
+        } else { // timelineMode is TimelineMode.TimeBoundEntries
+            val timeIntervals = (timelineMode as TimelineMode.TimeBoundEntries).timeIntervals
+            timeIntervals.forEach {
+                val result = runComposition(it).layout
+
+                timelineBuilders
+                    .addTimelineEntry(
+                        TimelineBuilders.TimelineEntry.Builder()
+                            .setValidity(
+                                TimelineBuilders.TimeInterval.Builder()
+                                    .setStartMillis(it.start.toEpochMilli())
+                                    .setEndMillis(it.end.toEpochMilli())
+                                    .build()
+                            )
+                            .setLayout(
+                                LayoutElementBuilders.Layout.Builder()
+                                    .setRoot(result)
+                                    .build()
+                            ).build()
+                    )
+            }
+        }
 
         TileBuilders.Tile.Builder()
             .setResourcesVersion(ResourcesVersion)
-            .setTimeline(
-                TimelineBuilders.Timeline.Builder()
-                    .addTimelineEntry(
-                        TimelineBuilders.TimelineEntry.Builder()
-                            .setLayout(
-                                LayoutElementBuilders.Layout.Builder()
-                                    .setRoot(content)
-                                    .build()
-                            ).build()
-                    ).build()
-            ).build()
+            .setTimeline(timelineBuilders.build())
+            .build()
     }
 
     /**
@@ -153,7 +191,21 @@
     final override fun onResourcesRequest(
         requestParams: RequestBuilders.ResourcesRequest
     ): ListenableFuture<ResourceBuilders.Resources> = coroutineScope.future {
-        runComposition().resources.setVersion(ResourcesVersion).build()
+        var resourceBuilder: ResourceBuilders.Resources.Builder
+        if (timelineMode === TimelineMode.SingleEntry) {
+            resourceBuilder = runComposition().resources
+        } else {
+            timelineMode is TimelineMode.TimeBoundEntries
+            resourceBuilder = ResourceBuilders.Resources.Builder()
+            val timeIntervals = (timelineMode as TimelineMode.TimeBoundEntries).timeIntervals
+            timeIntervals.forEach {
+                val result = runComposition(it).resources
+                result.build().idToImageMapping.forEach { res ->
+                    resourceBuilder.addIdToImageMapping(res.key, res.value)
+                }
+            }
+        }
+        resourceBuilder.setVersion(ResourcesVersion).build()
     }
 
     @VisibleForTesting
diff --git a/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/TimelineMode.kt b/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/TimelineMode.kt
new file mode 100644
index 0000000..4e92fa6
--- /dev/null
+++ b/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/TimelineMode.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2021 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.glance.wear
+
+import java.time.Instant
+
+/**
+ * TimeInterval class defines a period from [start] to [end]
+ *
+ * @param start The start time of the time interval
+ * @param end The end time of the time interval
+ */
+public data class TimeInterval(
+    val start: Instant = Instant.ofEpochMilli(0),
+    val end: Instant = Instant.ofEpochMilli(Long.MAX_VALUE)
+) {
+    init {
+        require(end > start) {
+            "End time shall come after start time to form a valid interval"
+        }
+    }
+}
+
+public sealed interface TimelineMode {
+    /**
+     * The [GlanceTileService] provides a single UI.
+     * The layout is fixed, and only the information inside the layout changes.
+     */
+    public object SingleEntry : TimelineMode {
+        public override fun toString(): String = "TimelineMode: SingleEntry"
+    }
+
+    /**
+     * The [GlanceTileService] provides a UI for a fixed set of time intervals
+     *
+     * @param timeIntervals Used to build the list of time intervals, the list must not be empty.
+     */
+    public class TimeBoundEntries(val timeIntervals: Set<TimeInterval>) : TimelineMode {
+        init {
+            require(timeIntervals.isNotEmpty()) { "The set of time intervals cannot be empty" }
+        }
+
+        public override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (javaClass != other?.javaClass) return false
+
+            other as TimeBoundEntries
+
+            if (timeIntervals != other.timeIntervals) return false
+
+            return true
+        }
+
+        public override fun hashCode(): Int = timeIntervals.hashCode()
+
+        public override fun toString(): String =
+            "TimelineMode.TimeBoundEntries(timeIntervals=$timeIntervals)"
+    }
+}
\ No newline at end of file
diff --git a/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/WearCompositionTranslator.kt b/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/WearCompositionTranslator.kt
index f78e8a6..76ff0b8 100644
--- a/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/WearCompositionTranslator.kt
+++ b/glance/glance-wear/src/androidMain/kotlin/androidx/glance/wear/WearCompositionTranslator.kt
@@ -133,6 +133,7 @@
     else -> error("Unsupported color provider: $this")
 }
 
+// TODO: handle parameters
 private fun LaunchActivityAction.toProto(context: Context): ActionBuilders.LaunchAction =
     ActionBuilders.LaunchAction.Builder()
         .setAndroidActivity(
diff --git a/glance/glance-wear/src/test/kotlin/androidx/glance/wear/GlanceTileServiceTest.kt b/glance/glance-wear/src/test/kotlin/androidx/glance/wear/GlanceTileServiceTest.kt
index fadf753..7133c06 100644
--- a/glance/glance-wear/src/test/kotlin/androidx/glance/wear/GlanceTileServiceTest.kt
+++ b/glance/glance-wear/src/test/kotlin/androidx/glance/wear/GlanceTileServiceTest.kt
@@ -18,9 +18,17 @@
 
 import android.os.Looper
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.dp
+import androidx.glance.GlanceModifier
+import androidx.glance.Image
+import androidx.glance.ImageProvider
+import androidx.glance.layout.ContentScale
+import androidx.glance.layout.size
 import androidx.glance.text.Text
+import androidx.glance.wear.test.R
 import androidx.wear.tiles.LayoutElementBuilders
 import androidx.wear.tiles.RequestBuilders
+import androidx.wear.tiles.TimelineBuilders
 import androidx.wear.tiles.testing.TestTileClient
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineDispatcher
@@ -33,6 +41,7 @@
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.Shadows.shadowOf
+import java.time.Instant
 import kotlin.test.assertIs
 
 @OptIn(ExperimentalCoroutinesApi::class, ExperimentalStdlibApi::class)
@@ -41,23 +50,30 @@
     private lateinit var fakeCoroutineScope: TestCoroutineScope
     private lateinit var tileService: TestGlanceTileService
     private lateinit var tileServiceClient: TestTileClient<GlanceTileService>
+    private lateinit var tileServiceWithTimeline: TestGlanceTileServiceWithTimeline
+    private lateinit var tileServiceClientWithTimeline: TestTileClient<GlanceTileService>
 
     @Before
     fun setUp() {
         fakeCoroutineScope = TestCoroutineScope()
+
         tileService = TestGlanceTileService()
         tileServiceClient = TestTileClient(
             tileService,
             fakeCoroutineScope,
             fakeCoroutineScope.coroutineContext[CoroutineDispatcher]!!
         )
+
+        tileServiceWithTimeline = TestGlanceTileServiceWithTimeline()
+        tileServiceClientWithTimeline = TestTileClient(
+            tileServiceWithTimeline,
+            fakeCoroutineScope,
+            fakeCoroutineScope.coroutineContext[CoroutineDispatcher]!!
+        )
     }
 
     @Test
     fun tileProviderReturnsTile() = fakeCoroutineScope.runBlockingTest {
-        // Add the composition to the service.
-        tileService.actualContent = { Text("Hello World!") }
-
         // Request is currently un-used, provide an empty one.
         val tileRequest = RequestBuilders.TileRequest.Builder().build()
 
@@ -79,16 +95,62 @@
 
         // It always emits a box as the root-level layout.
         val box = assertIs<LayoutElementBuilders.Box>(entry.layout!!.root!!)
-        assertThat(box.contents).hasSize(1)
+        assertThat(box.contents).hasSize(2)
         val text = assertIs<LayoutElementBuilders.Text>(box.contents[0])
 
         assertThat(text.text!!.value).isEqualTo("Hello World!")
     }
 
     @Test
+    fun tileProviderReturnsTimelineTile() = fakeCoroutineScope.runBlockingTest {
+        // Request is currently un-used, provide an empty one.
+        val tileRequest = RequestBuilders.TileRequest.Builder().build()
+
+        // Requests need to be split; we need to allow Robolectric to schedule the service calls on
+        // the main looper, so we can't just do requestTile().await().
+        val tileFuture = tileServiceClientWithTimeline.requestTile(tileRequest)
+        shadowOf(Looper.getMainLooper()).idle()
+        val tile = tileFuture.await()
+
+        // Just uses a simple resource version for now.
+        assertThat(tile.resourcesVersion).isEqualTo(GlanceTileService.ResourcesVersion)
+
+        // No freshness interval (for now)
+        assertThat(tile.freshnessIntervalMillis).isEqualTo(0)
+
+        assertThat(tile.timeline!!.timelineEntries).hasSize(4)
+
+        checkTimelineEntry(
+            tile.timeline!!.timelineEntries[0],
+            0,
+            Long.MAX_VALUE,
+            "No event"
+        )
+
+        checkTimelineEntry(
+            tile.timeline!!.timelineEntries[1],
+            time1.toEpochMilli(),
+            time2.toEpochMilli(),
+            "Coffee"
+        )
+
+        checkTimelineEntry(
+            tile.timeline!!.timelineEntries[2],
+            time2.toEpochMilli(),
+            time3.toEpochMilli(),
+            "Work"
+        )
+
+        checkTimelineEntry(
+            tile.timeline!!.timelineEntries[3],
+            time4.toEpochMilli(),
+            Long.MAX_VALUE,
+            "Dinner"
+        )
+    }
+
+    @Test
     fun tileProviderReturnsResources() = fakeCoroutineScope.runBlockingTest {
-        // The service doesn't use resources right now, but it must correctly respond to
-        // onResourcesRequest. Just ensure that the version is set correctly.
         val resourcesRequest = RequestBuilders.ResourcesRequest.Builder().build()
 
         val resourcesFuture = tileServiceClient.requestResources(resourcesRequest)
@@ -96,14 +158,91 @@
         val resources = resourcesFuture.await()
 
         assertThat(resources.version).isEqualTo(GlanceTileService.ResourcesVersion)
+        assertThat(resources.idToImageMapping.size).isEqualTo(1)
+        assertThat(resources.idToImageMapping.containsKey("android_" + R.drawable.oval)).isTrue()
+    }
+
+    @Test
+    fun tileProviderReturnsTimelineResources() = fakeCoroutineScope.runBlockingTest {
+        val resourcesRequest = RequestBuilders.ResourcesRequest.Builder().build()
+
+        val resourcesFuture = tileServiceClientWithTimeline.requestResources(resourcesRequest)
+        shadowOf(Looper.getMainLooper()).idle()
+        val resources = resourcesFuture.await()
+
+        assertThat(resources.version).isEqualTo(GlanceTileService.ResourcesVersion)
+        assertThat(resources.idToImageMapping.size).isEqualTo(2)
+        assertThat(resources.idToImageMapping.containsKey("android_" + R.drawable.oval)).isTrue()
+        assertThat(
+            resources.idToImageMapping.containsKey("android_" + R.drawable.ic_launcher_background)
+        ).isTrue()
+    }
+
+    private fun checkTimelineEntry(
+        entry: TimelineBuilders.TimelineEntry,
+        startMillis: Long,
+        endMillis: Long,
+        textValue: String
+    ) {
+        assertThat(entry.validity!!.startMillis).isEqualTo(startMillis)
+        assertThat(entry.validity!!.endMillis).isEqualTo(endMillis)
+        var box = assertIs<LayoutElementBuilders.Box>(entry.layout!!.root!!)
+        var text = assertIs<LayoutElementBuilders.Text>(box.contents[0])
+        assertThat(text.text!!.value).isEqualTo(textValue)
     }
 
     private inner class TestGlanceTileService : GlanceTileService() {
-        var actualContent: @Composable () -> Unit = {}
-
         @Composable
         override fun Content() {
-            actualContent()
+            Text("Hello World!")
+            Image(
+                provider = ImageProvider(R.drawable.oval),
+                contentDescription = "Oval",
+                modifier = GlanceModifier.size(40.dp),
+                contentScale = ContentScale.FillBounds
+            )
         }
     }
+
+     private inner class TestGlanceTileServiceWithTimeline : GlanceTileService() {
+         override val timelineMode = testTimelineMode
+
+         @Composable
+         override fun Content() {
+             when (LocalTimeInterval.current) {
+                 testTimelineMode.timeIntervals.elementAt(0) -> { Text("No event") }
+                 testTimelineMode.timeIntervals.elementAt(1) -> {
+                     Text("Coffee")
+                     Image(
+                         provider = ImageProvider(R.drawable.oval),
+                         contentDescription = "Oval",
+                         modifier = GlanceModifier.size(40.dp),
+                         contentScale = ContentScale.FillBounds
+                     )
+                 }
+                 testTimelineMode.timeIntervals.elementAt(2) -> {
+                     Text("Work")
+                     Image(
+                         provider = ImageProvider(R.drawable.ic_launcher_background),
+                         contentDescription = "Icon",
+                         modifier = GlanceModifier.size(40.dp),
+                     )
+                 }
+                 testTimelineMode.timeIntervals.elementAt(3) -> { Text("Dinner") }
+             }
+         }
+     }
+
+    private companion object {
+        private val time1 = Instant.parse("2021-11-12T13:15:30.00Z")
+        private val time2 = Instant.parse("2021-11-12T13:45:30.00Z")
+        private val time3 = Instant.parse("2021-11-12T17:45:30.00Z")
+        private val time4 = Instant.parse("2021-11-12T18:30:30.00Z")
+        val testTimelineMode = TimelineMode.TimeBoundEntries(setOf(
+            TimeInterval(),
+            TimeInterval(time1, time2),
+            TimeInterval(time2, time3),
+            TimeInterval(time4)
+        ))
+    }
 }
\ No newline at end of file
diff --git a/glance/glance-wear/src/test/kotlin/androidx/glance/wear/TimelineModeTest.kt b/glance/glance-wear/src/test/kotlin/androidx/glance/wear/TimelineModeTest.kt
new file mode 100644
index 0000000..d044d94
--- /dev/null
+++ b/glance/glance-wear/src/test/kotlin/androidx/glance/wear/TimelineModeTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 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.glance.wear
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import java.time.Instant
+
+class TimelineModeTest {
+
+    @Test
+    fun timeboundEntriesTest() {
+        val time1 = Instant.parse("2021-11-12T13:15:30.00Z")
+        val time2 = Instant.parse("2021-11-12T13:45:30.00Z")
+        val time3 = Instant.parse("2021-11-12T17:45:30.00Z")
+        val time4 = Instant.parse("2021-11-12T18:30:30.00Z")
+
+        val timeBoundEntries = TimelineMode.TimeBoundEntries(setOf(
+            TimeInterval(),
+            TimeInterval(time1, time2),
+            TimeInterval(time2, time3),
+            TimeInterval(time4)
+        ))
+        val intervals = timeBoundEntries.timeIntervals
+
+        assertThat(intervals.size).isEqualTo(4)
+
+        assertThat(intervals.elementAt(0).start.toEpochMilli()).isEqualTo(0)
+        assertThat(intervals.elementAt(0).end.toEpochMilli()).isEqualTo(Long.MAX_VALUE)
+
+        assertThat(intervals.elementAt(1).start).isEqualTo(time1)
+        assertThat(intervals.elementAt(1).end).isEqualTo(time2)
+
+        assertThat(intervals.elementAt(2).start).isEqualTo(time2)
+        assertThat(intervals.elementAt(2).end).isEqualTo(time3)
+
+        assertThat(intervals.elementAt(3).start).isEqualTo(time4)
+        assertThat(intervals.elementAt(3).end.toEpochMilli()).isEqualTo(Long.MAX_VALUE)
+    }
+}
\ No newline at end of file
diff --git a/glance/glance-wear/src/test/kotlin/androidx/glance/wear/WearCompositionTranslatorTest.kt b/glance/glance-wear/src/test/kotlin/androidx/glance/wear/WearCompositionTranslatorTest.kt
index c133aaa..03b2150 100644
--- a/glance/glance-wear/src/test/kotlin/androidx/glance/wear/WearCompositionTranslatorTest.kt
+++ b/glance/glance-wear/src/test/kotlin/androidx/glance/wear/WearCompositionTranslatorTest.kt
@@ -582,7 +582,9 @@
         )
         assertThat(image.resourceId!!.value).isEqualTo("android_" + R.drawable.oval)
 
-        assertThat(resources.build().idToImageMapping.containsKey(R.drawable.oval.toString()))
+        assertThat(
+            resources.build().idToImageMapping.containsKey("android_" + R.drawable.oval)
+        ).isTrue()
 
         assertThat(image.modifiers!!.semantics!!.contentDescription).isEqualTo("Oval")
     }
diff --git a/glance/glance-wear/src/test/res/drawable/ic_launcher_background.xml b/glance/glance-wear/src/test/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..ca3826a
--- /dev/null
+++ b/glance/glance-wear/src/test/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector
+    android:height="108dp"
+    android:width="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#3DDC84"
+          android:pathData="M0,0h108v108h-108z"/>
+    <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+</vector>
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index 56e1253..5955625 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -117,12 +117,13 @@
     method public static androidx.glance.action.MutableActionParameters mutableActionParametersOf(androidx.glance.action.ActionParameters.Pair<?>... pairs);
     method public static androidx.glance.action.MutableActionParameters toMutableParameters(androidx.glance.action.ActionParameters);
     method public static androidx.glance.action.ActionParameters toParameters(androidx.glance.action.ActionParameters);
+    method public static <T> androidx.glance.action.ActionParameters.Key<T> toParametersKey(androidx.datastore.preferences.core.Preferences.Key<T>);
   }
 
   public final class LaunchActivityActionKt {
-    method public static androidx.glance.action.Action actionLaunchActivity(android.content.ComponentName componentName);
-    method public static <T extends android.app.Activity> androidx.glance.action.Action actionLaunchActivity(Class<T> activity);
-    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! actionLaunchActivity();
+    method public static androidx.glance.action.Action actionLaunchActivity(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
+    method public static <T extends android.app.Activity> androidx.glance.action.Action actionLaunchActivity(Class<T> activity, optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! actionLaunchActivity(optional androidx.glance.action.ActionParameters parameters);
   }
 
   public final class MutableActionParameters extends androidx.glance.action.ActionParameters {
@@ -276,15 +277,15 @@
 
 package androidx.glance.state {
 
-  public final class GlanceState {
-    method public suspend <T> Object? getValue(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, String fileName, kotlin.coroutines.Continuation<? super T> p);
-    method public suspend <T> Object? updateValue(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, String fileName, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateBlock, kotlin.coroutines.Continuation<? super T> p);
-    field public static final androidx.glance.state.GlanceState INSTANCE;
+  public interface GlanceStateDefinition<T> {
+    method public suspend Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<T>> p);
+    method public java.io.File getLocation(android.content.Context context, String fileKey);
   }
 
-  public interface GlanceStateDefinition<T> {
-    method public suspend <T> Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<T>> p);
+  public final class PreferencesGlanceStateDefinition implements androidx.glance.state.GlanceStateDefinition<androidx.datastore.preferences.core.Preferences> {
+    method public suspend Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>> p);
     method public java.io.File getLocation(android.content.Context context, String fileKey);
+    field public static final androidx.glance.state.PreferencesGlanceStateDefinition INSTANCE;
   }
 
 }
diff --git a/glance/glance/api/public_plus_experimental_current.txt b/glance/glance/api/public_plus_experimental_current.txt
index 56e1253..5955625 100644
--- a/glance/glance/api/public_plus_experimental_current.txt
+++ b/glance/glance/api/public_plus_experimental_current.txt
@@ -117,12 +117,13 @@
     method public static androidx.glance.action.MutableActionParameters mutableActionParametersOf(androidx.glance.action.ActionParameters.Pair<?>... pairs);
     method public static androidx.glance.action.MutableActionParameters toMutableParameters(androidx.glance.action.ActionParameters);
     method public static androidx.glance.action.ActionParameters toParameters(androidx.glance.action.ActionParameters);
+    method public static <T> androidx.glance.action.ActionParameters.Key<T> toParametersKey(androidx.datastore.preferences.core.Preferences.Key<T>);
   }
 
   public final class LaunchActivityActionKt {
-    method public static androidx.glance.action.Action actionLaunchActivity(android.content.ComponentName componentName);
-    method public static <T extends android.app.Activity> androidx.glance.action.Action actionLaunchActivity(Class<T> activity);
-    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! actionLaunchActivity();
+    method public static androidx.glance.action.Action actionLaunchActivity(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
+    method public static <T extends android.app.Activity> androidx.glance.action.Action actionLaunchActivity(Class<T> activity, optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! actionLaunchActivity(optional androidx.glance.action.ActionParameters parameters);
   }
 
   public final class MutableActionParameters extends androidx.glance.action.ActionParameters {
@@ -276,15 +277,15 @@
 
 package androidx.glance.state {
 
-  public final class GlanceState {
-    method public suspend <T> Object? getValue(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, String fileName, kotlin.coroutines.Continuation<? super T> p);
-    method public suspend <T> Object? updateValue(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, String fileName, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateBlock, kotlin.coroutines.Continuation<? super T> p);
-    field public static final androidx.glance.state.GlanceState INSTANCE;
+  public interface GlanceStateDefinition<T> {
+    method public suspend Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<T>> p);
+    method public java.io.File getLocation(android.content.Context context, String fileKey);
   }
 
-  public interface GlanceStateDefinition<T> {
-    method public suspend <T> Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<T>> p);
+  public final class PreferencesGlanceStateDefinition implements androidx.glance.state.GlanceStateDefinition<androidx.datastore.preferences.core.Preferences> {
+    method public suspend Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>> p);
     method public java.io.File getLocation(android.content.Context context, String fileKey);
+    field public static final androidx.glance.state.PreferencesGlanceStateDefinition INSTANCE;
   }
 
 }
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index 56e1253..5955625 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -117,12 +117,13 @@
     method public static androidx.glance.action.MutableActionParameters mutableActionParametersOf(androidx.glance.action.ActionParameters.Pair<?>... pairs);
     method public static androidx.glance.action.MutableActionParameters toMutableParameters(androidx.glance.action.ActionParameters);
     method public static androidx.glance.action.ActionParameters toParameters(androidx.glance.action.ActionParameters);
+    method public static <T> androidx.glance.action.ActionParameters.Key<T> toParametersKey(androidx.datastore.preferences.core.Preferences.Key<T>);
   }
 
   public final class LaunchActivityActionKt {
-    method public static androidx.glance.action.Action actionLaunchActivity(android.content.ComponentName componentName);
-    method public static <T extends android.app.Activity> androidx.glance.action.Action actionLaunchActivity(Class<T> activity);
-    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! actionLaunchActivity();
+    method public static androidx.glance.action.Action actionLaunchActivity(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
+    method public static <T extends android.app.Activity> androidx.glance.action.Action actionLaunchActivity(Class<T> activity, optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action! actionLaunchActivity(optional androidx.glance.action.ActionParameters parameters);
   }
 
   public final class MutableActionParameters extends androidx.glance.action.ActionParameters {
@@ -276,15 +277,15 @@
 
 package androidx.glance.state {
 
-  public final class GlanceState {
-    method public suspend <T> Object? getValue(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, String fileName, kotlin.coroutines.Continuation<? super T> p);
-    method public suspend <T> Object? updateValue(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, String fileName, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateBlock, kotlin.coroutines.Continuation<? super T> p);
-    field public static final androidx.glance.state.GlanceState INSTANCE;
+  public interface GlanceStateDefinition<T> {
+    method public suspend Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<T>> p);
+    method public java.io.File getLocation(android.content.Context context, String fileKey);
   }
 
-  public interface GlanceStateDefinition<T> {
-    method public suspend <T> Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<T>> p);
+  public final class PreferencesGlanceStateDefinition implements androidx.glance.state.GlanceStateDefinition<androidx.datastore.preferences.core.Preferences> {
+    method public suspend Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>> p);
     method public java.io.File getLocation(android.content.Context context, String fileKey);
+    field public static final androidx.glance.state.PreferencesGlanceStateDefinition INSTANCE;
   }
 
 }
diff --git a/glance/glance/build.gradle b/glance/glance/build.gradle
index 1bd9e3b..a01b578 100644
--- a/glance/glance/build.gradle
+++ b/glance/glance/build.gradle
@@ -36,6 +36,8 @@
     api("androidx.compose.ui:ui-graphics:1.1.0-beta01")
     api("androidx.compose.ui:ui-unit:1.1.0-beta01")
     api("androidx.datastore:datastore-core:1.0.0")
+    api("androidx.datastore:datastore-preferences-core:1.0.0")
+    api("androidx.datastore:datastore-preferences:1.0.0")
 
     implementation("androidx.annotation:annotation:1.1.0")
     implementation(libs.kotlinStdlib)
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/action/ActionParameters.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/action/ActionParameters.kt
index 40ecf1d..eb02d71 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/action/ActionParameters.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/action/ActionParameters.kt
@@ -16,6 +16,7 @@
 
 package androidx.glance.action
 
+import androidx.datastore.preferences.core.Preferences
 import java.util.Collections
 
 /**
@@ -197,3 +198,7 @@
  * @return a copy of this Parameters
  */
 public fun ActionParameters.toParameters(): ActionParameters = toMutableParameters()
+
+/** Creates an action key from a preferences key. */
+public fun <T : Any> Preferences.Key<T>.toParametersKey(): ActionParameters.Key<T> =
+    ActionParameters.Key(name)
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/action/LaunchActivityAction.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/action/LaunchActivityAction.kt
index 387893d..ff1e90b 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/action/LaunchActivityAction.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/action/LaunchActivityAction.kt
@@ -22,39 +22,55 @@
 
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public interface LaunchActivityAction : Action
+public interface LaunchActivityAction : Action {
+    abstract val parameters: ActionParameters
+}
 
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class LaunchActivityComponentAction(
-    public val componentName: ComponentName
+    public val componentName: ComponentName,
+    public override val parameters: ActionParameters
 ) : LaunchActivityAction
 
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class LaunchActivityClassAction(
-    public val activityClass: Class<out Activity>
+    public val activityClass: Class<out Activity>,
+    public override val parameters: ActionParameters
 ) : LaunchActivityAction
 
 /**
  * Creates an [Action] that launches the [Activity] specified by the given [ComponentName].
  *
  * @param componentName component of the activity to launch
+ * @param parameters the parameters associated with the action. Parameter values will be added to
+ * the activity intent, keyed by the parameter key name string.
  */
-public fun actionLaunchActivity(componentName: ComponentName): Action =
-    LaunchActivityComponentAction(componentName)
+public fun actionLaunchActivity(
+    componentName: ComponentName,
+    parameters: ActionParameters = actionParametersOf()
+): Action = LaunchActivityComponentAction(componentName, parameters)
 
 /**
  * Creates an [Action] that launches the specified [Activity] when triggered.
  *
  * @param activity class of the activity to launch
+ * @param parameters the parameters associated with the action. Parameter values will be added to
+ * the activity intent, keyed by the parameter key name string.
  */
-public fun <T : Activity> actionLaunchActivity(activity: Class<T>): Action =
-    LaunchActivityClassAction(activity)
+public fun <T : Activity> actionLaunchActivity(
+    activity: Class<T>,
+    parameters: ActionParameters = actionParametersOf()
+): Action = LaunchActivityClassAction(activity, parameters)
 
 @Suppress("MissingNullability") /* Shouldn't need to specify @NonNull. b/199284086 */
 /**
  * Creates an [Action] that launches the specified [Activity] when triggered.
+ *
+ * @param parameters the parameters associated with the action. Parameter values will be added to
+ * the activity intent, keyed by the parameter key name string.
  */
-public inline fun <reified T : Activity> actionLaunchActivity(): Action =
-    actionLaunchActivity(T::class.java)
+public inline fun <reified T : Activity> actionLaunchActivity(
+    parameters: ActionParameters = actionParametersOf()
+): Action = actionLaunchActivity(T::class.java, parameters)
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/state/GlanceStateDefinition.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/state/GlanceStateDefinition.kt
index ca1cc33..3be6ce2 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/state/GlanceStateDefinition.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/state/GlanceStateDefinition.kt
@@ -17,7 +17,11 @@
 package androidx.glance.state
 
 import android.content.Context
+import androidx.annotation.RestrictTo
 import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.PreferenceDataStoreFactory
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.preferencesDataStoreFile
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
@@ -46,14 +50,17 @@
      * @param fileKey The unique string key used to name and identify the data file corresponding to
      * a remote UI. Each remote UI has a unique UI key, used to key the data for that UI.
      */
-    public suspend fun <T> getDataStore(context: Context, fileKey: String): DataStore<T>
+    public suspend fun getDataStore(context: Context, fileKey: String): DataStore<T>
 }
 
 /**
  * Data store for data specific to the glanceable view. Stored data should include information
  * relevant to the representation of views, but not surface specific view data. For example, the
  * month displayed on a calendar rather than actual calendar entries.
+ *
+ * @suppress
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public object GlanceState {
     // TODO(b/205496180): Make methods internal
     /**
@@ -81,7 +88,7 @@
         definition: GlanceStateDefinition<T>,
         fileName: String,
         updateBlock: suspend (T) -> T
-    ) = getDataStore(context, definition, fileName).updateData(updateBlock)
+    ): T = getDataStore(context, definition, fileName).updateData(updateBlock)
 
     @Suppress("UNCHECKED_CAST")
     private suspend fun <T> getDataStore(
@@ -91,7 +98,7 @@
     ): DataStore<T> =
         mutex.withLock {
             dataStores.getOrPut(fileName) {
-                definition.getDataStore<T>(context, fileName)
+                definition.getDataStore(context, fileName)
             } as DataStore<T>
         }
 
@@ -100,3 +107,14 @@
     // TODO: Move to a single, global source to manage the data lifecycle
     private val dataStores: MutableMap<String, DataStore<*>> = mutableMapOf()
 }
+
+/**
+ * Base class helping the creation of a state using DataStore's [Preferences].
+ */
+object PreferencesGlanceStateDefinition : GlanceStateDefinition<Preferences> {
+    override fun getLocation(context: Context, fileKey: String): File =
+        context.preferencesDataStoreFile(fileKey)
+
+    override suspend fun getDataStore(context: Context, fileKey: String): DataStore<Preferences> =
+        PreferenceDataStoreFactory.create { context.preferencesDataStoreFile(fileKey) }
+}
diff --git a/glance/glance/src/test/kotlin/androidx/glance/ButtonTest.kt b/glance/glance/src/test/kotlin/androidx/glance/ButtonTest.kt
index 6ae0921..6f49d6a2 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/ButtonTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/ButtonTest.kt
@@ -17,8 +17,10 @@
 
 import android.app.Activity
 import androidx.glance.action.ActionModifier
+import androidx.glance.action.ActionParameters
 import androidx.glance.action.LaunchActivityAction
 import androidx.glance.action.actionLaunchActivity
+import androidx.glance.action.actionParametersOf
 import androidx.glance.layout.runTestingComposition
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -39,15 +41,26 @@
 
     @Test
     fun createComposableButton() = fakeCoroutineScope.runBlockingTest {
+        val stringKey = ActionParameters.Key<String>("test")
+        val intKey = ActionParameters.Key<Int>("test2")
+        val string = "testString"
+        val int = 12
+
         val root = runTestingComposition {
-            Button(text = "button", onClick = actionLaunchActivity<Activity>(), enabled = true)
+            Button(text = "button", onClick = actionLaunchActivity<Activity>(
+                actionParametersOf(stringKey to string, intKey to int)
+            ), enabled = true)
         }
 
         assertThat(root.children).hasSize(1)
         val child = assertIs<EmittableButton>(root.children[0])
         assertThat(child.text).isEqualTo("button")
-        assertIs<LaunchActivityAction>(child.modifier.findModifier<ActionModifier>()?.action)
+        val action =
+            assertIs<LaunchActivityAction>(child.modifier.findModifier<ActionModifier>()?.action)
         assertThat(child.enabled).isTrue()
+        assertThat(action.parameters.asMap()).hasSize(2)
+        assertThat(action.parameters[stringKey]).isEqualTo(string)
+        assertThat(action.parameters[intKey]).isEqualTo(int)
     }
 
     @Test
diff --git a/glance/glance/src/test/kotlin/androidx/glance/state/StateDefinitionTest.kt b/glance/glance/src/test/kotlin/androidx/glance/state/StateDefinitionTest.kt
index 6610c26..91a054a 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/state/StateDefinitionTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/state/StateDefinitionTest.kt
@@ -17,12 +17,8 @@
 package androidx.glance.state
 
 import android.content.Context
-import androidx.datastore.core.DataStore
-import androidx.datastore.preferences.core.PreferenceDataStoreFactory
-import androidx.datastore.preferences.core.Preferences
 import androidx.datastore.preferences.core.intPreferencesKey
 import androidx.datastore.preferences.core.stringPreferencesKey
-import androidx.datastore.preferences.preferencesDataStoreFile
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,7 +26,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
-import java.io.File
 import kotlin.test.assertNotNull
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -45,7 +40,13 @@
     fun emptyStore() {
         runBlocking {
             val uiString = "testUiString1"
-            val store = assertNotNull(GlanceState.getValue(context, EmptyStateDefinition, uiString))
+            val store = assertNotNull(
+                GlanceState.getValue(
+                    context,
+                    PreferencesGlanceStateDefinition,
+                    uiString
+                )
+            )
             assertThat(store.contains(counterKey)).isFalse()
             assertThat(store.contains(stringKey)).isFalse()
         }
@@ -55,20 +56,32 @@
     fun counterKey() {
         runBlocking {
             val uiString = "testUiString2"
-            GlanceState.updateValue(context, EmptyStateDefinition, uiString) { prefs ->
+            GlanceState.updateValue(context, PreferencesGlanceStateDefinition, uiString) { prefs ->
                 prefs.toMutablePreferences().apply { this[counterKey] = 0 }.toPreferences()
             }
 
-            var store = assertNotNull(GlanceState.getValue(context, EmptyStateDefinition, uiString))
+            var store = assertNotNull(
+                GlanceState.getValue(
+                    context,
+                    PreferencesGlanceStateDefinition,
+                    uiString
+                )
+            )
             assertThat(store.contains(counterKey)).isTrue()
             assertThat(store[counterKey]).isEqualTo(0)
 
-            GlanceState.updateValue(context, EmptyStateDefinition, uiString) { prefs ->
+            GlanceState.updateValue(context, PreferencesGlanceStateDefinition, uiString) { prefs ->
                 prefs.toMutablePreferences().apply {
                     this[counterKey] = prefs[counterKey]!! + 1
                 }.toPreferences()
             }
-            store = assertNotNull(GlanceState.getValue(context, EmptyStateDefinition, uiString))
+            store = assertNotNull(
+                GlanceState.getValue(
+                    context,
+                    PreferencesGlanceStateDefinition,
+                    uiString
+                )
+            )
             assertThat(store.contains(counterKey)).isTrue()
             assertThat(store[counterKey]).isEqualTo(1)
         }
@@ -81,36 +94,37 @@
         val storedMessage2 = "Another test string"
 
         runBlocking {
-            GlanceState.updateValue(context, EmptyStateDefinition, uiString) { prefs ->
+            GlanceState.updateValue(context, PreferencesGlanceStateDefinition, uiString) { prefs ->
                 prefs.toMutablePreferences().apply { this[stringKey] = storedMessage1 }
                     .toPreferences()
             }
 
-            var store = assertNotNull(GlanceState.getValue(context, EmptyStateDefinition, uiString))
+            var store = assertNotNull(
+                GlanceState.getValue(
+                    context,
+                    PreferencesGlanceStateDefinition,
+                    uiString
+                )
+            )
             assertThat(store.contains(counterKey)).isFalse()
             assertThat(store.contains(stringKey)).isTrue()
             assertThat(store[stringKey]).isEqualTo(storedMessage1)
 
-            GlanceState.updateValue(context, EmptyStateDefinition, uiString) { prefs ->
+            GlanceState.updateValue(context, PreferencesGlanceStateDefinition, uiString) { prefs ->
                 prefs.toMutablePreferences().apply {
                     this[stringKey] = storedMessage2
                 }.toPreferences()
             }
-            store = assertNotNull(GlanceState.getValue(context, EmptyStateDefinition, uiString))
+            store = assertNotNull(
+                GlanceState.getValue(
+                    context,
+                    PreferencesGlanceStateDefinition,
+                    uiString
+                )
+            )
             assertThat(store.contains(counterKey)).isFalse()
             assertThat(store.contains(stringKey)).isTrue()
             assertThat(store[stringKey]).isEqualTo(storedMessage2)
         }
     }
-
-    object EmptyStateDefinition : GlanceStateDefinition<Preferences> {
-        override fun getLocation(context: Context, fileKey: String): File =
-            context.preferencesDataStoreFile(fileKey)
-
-        @Suppress("UNCHECKED_CAST")
-        override suspend fun <T> getDataStore(context: Context, fileKey: String): DataStore<T> =
-            PreferenceDataStoreFactory.create {
-                context.preferencesDataStoreFile(fileKey)
-            } as DataStore<T>
-    }
 }
\ No newline at end of file
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
index de040cf..50e6f8d 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/DexInspectorTask.kt
@@ -25,6 +25,7 @@
 import org.gradle.api.file.ConfigurableFileCollection
 import org.gradle.api.file.DirectoryProperty
 import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.tasks.CacheableTask
 import org.gradle.api.tasks.Classpath
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.InputFile
@@ -42,6 +43,7 @@
 import java.io.File
 import java.nio.charset.Charset
 
+@CacheableTask
 abstract class DexInspectorTask : DefaultTask() {
     @get:PathSensitive(PathSensitivity.NONE)
     @get:InputFile
diff --git a/jetifier/OWNERS b/jetifier/OWNERS
deleted file mode 100644
index 8ae348f..0000000
--- a/jetifier/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-pavlis@google.com
diff --git a/lifecycle/lifecycle-runtime-ktx-lint/build.gradle b/lifecycle/lifecycle-runtime-ktx-lint/build.gradle
index f0497c3..8776e4f 100644
--- a/lifecycle/lifecycle-runtime-ktx-lint/build.gradle
+++ b/lifecycle/lifecycle-runtime-ktx-lint/build.gradle
@@ -28,6 +28,8 @@
     compileOnly(libs.kotlinStdlib)
 
     testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.kotlinReflect)
+    testImplementation(libs.kotlinStdlibJdk8)
     testImplementation(libs.androidLint)
     testImplementation(libs.androidLintTests)
     testImplementation(libs.junit)
diff --git a/lifecycle/lifecycle-runtime-ktx-lint/src/main/java/androidx/lifecycle/lint/LifecycleWhenChecks.kt b/lifecycle/lifecycle-runtime-ktx-lint/src/main/java/androidx/lifecycle/lint/LifecycleWhenChecks.kt
index a1d8509..f863f3c 100644
--- a/lifecycle/lifecycle-runtime-ktx-lint/src/main/java/androidx/lifecycle/lint/LifecycleWhenChecks.kt
+++ b/lifecycle/lifecycle-runtime-ktx-lint/src/main/java/androidx/lifecycle/lint/LifecycleWhenChecks.kt
@@ -42,7 +42,9 @@
 import org.jetbrains.uast.UIfExpression
 import org.jetbrains.uast.ULambdaExpression
 import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.USwitchClauseExpression
 import org.jetbrains.uast.UTryExpression
+import org.jetbrains.uast.kotlin.KotlinUSwitchEntry
 import org.jetbrains.uast.toUElement
 import org.jetbrains.uast.tryResolve
 import org.jetbrains.uast.visitor.AbstractUastVisitor
@@ -190,6 +192,23 @@
         }
         return false
     }
+
+    override fun visitSwitchClauseExpression(node: USwitchClauseExpression): Boolean {
+        // check each case in the switch statement
+        node.caseValues.forEach { expression ->
+            val method = expression.tryResolve() as? PsiMethod ?: return false
+            if (method.isLifecycleIsAtLeastMethod(context)) {
+                // If the case containing the lifecycle check evaluates to true, check the body
+                withNewState(checkUIAccess = false) {
+                    (node as? KotlinUSwitchEntry)?.body?.expressions?.forEach {
+                        it.accept(this)
+                    }
+                }
+                return true
+            }
+        }
+        return false
+    }
 }
 
 private const val DISPATCHER_CLASS_NAME = "androidx.lifecycle.PausingDispatcherKt"
diff --git a/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/LifecycleWhenChecksTest.kt b/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/LifecycleWhenChecksTest.kt
index dbba1ac..e45da05 100644
--- a/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/LifecycleWhenChecksTest.kt
+++ b/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/LifecycleWhenChecksTest.kt
@@ -24,7 +24,6 @@
 import com.android.tools.lint.checks.infrastructure.TestFiles.kt
 import com.android.tools.lint.checks.infrastructure.TestLintResult
 import com.android.tools.lint.checks.infrastructure.TestLintTask
-import com.android.tools.lint.checks.infrastructure.TestMode
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -35,8 +34,6 @@
     private fun check(body: String): TestLintResult {
         return TestLintTask.lint()
             .files(VIEW_STUB, LIFECYCLE_STUB, COROUTINES_STUB, kt(template(body)))
-            .allowCompilationErrors(true) // b/193267317
-            .skipTestModes(TestMode.IF_TO_WHEN) // b/203245752
             .issues(ISSUE)
             .run()
     }
diff --git a/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/RepeatOnLifecycleDetectorTest.kt b/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/RepeatOnLifecycleDetectorTest.kt
index cce75cd..aa6e8ef 100644
--- a/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/RepeatOnLifecycleDetectorTest.kt
+++ b/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/RepeatOnLifecycleDetectorTest.kt
@@ -107,7 +107,6 @@
                 *REPEAT_ON_LIFECYCLE_STUBS,
                 TestFiles.kt(fileToAdd)
             )
-            .allowCompilationErrors(true) // b/193267317
             .issues(RepeatOnLifecycleDetector.ISSUE)
             .run()
     }
diff --git a/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/WhenMethodsTest.kt b/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/WhenMethodsTest.kt
index 5b26766..701f1da 100644
--- a/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/WhenMethodsTest.kt
+++ b/lifecycle/lifecycle-runtime-ktx-lint/src/test/java/androidx/lifecycle/runtime/lint/WhenMethodsTest.kt
@@ -51,7 +51,6 @@
 
     private fun check(body: String): TestLintResult {
         return TestLintTask.lint()
-            .allowCompilationErrors(true) // b/193267317
             .files(VIEW_STUB, LIFECYCLE_STUB, COROUTINES_STUB, TestFiles.kt(template(body)))
             .issues(LifecycleWhenChecks.ISSUE)
             .run()
diff --git a/mediarouter/mediarouter/api/current.txt b/mediarouter/mediarouter/api/current.txt
index 6af2727..a750177 100644
--- a/mediarouter/mediarouter/api/current.txt
+++ b/mediarouter/mediarouter/api/current.txt
@@ -452,7 +452,7 @@
 
   public class MediaRouterParams {
     method public int getDialogType();
-    method public boolean isMediaTransferReceiverDisabled();
+    method public boolean isMediaTransferReceiverEnabled();
     method public boolean isOutputSwitcherEnabled();
     method public boolean isTransferToLocalEnabled();
     field public static final int DIALOG_TYPE_DEFAULT = 1; // 0x1
@@ -465,7 +465,7 @@
     ctor public MediaRouterParams.Builder(androidx.mediarouter.media.MediaRouterParams);
     method public androidx.mediarouter.media.MediaRouterParams build();
     method public androidx.mediarouter.media.MediaRouterParams.Builder setDialogType(int);
-    method public androidx.mediarouter.media.MediaRouterParams.Builder setMediaTransferReceiverDisabled(boolean);
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setMediaTransferReceiverEnabled(boolean);
     method public androidx.mediarouter.media.MediaRouterParams.Builder setOutputSwitcherEnabled(boolean);
     method public androidx.mediarouter.media.MediaRouterParams.Builder setTransferToLocalEnabled(boolean);
   }
diff --git a/mediarouter/mediarouter/api/public_plus_experimental_current.txt b/mediarouter/mediarouter/api/public_plus_experimental_current.txt
index 6af2727..a750177 100644
--- a/mediarouter/mediarouter/api/public_plus_experimental_current.txt
+++ b/mediarouter/mediarouter/api/public_plus_experimental_current.txt
@@ -452,7 +452,7 @@
 
   public class MediaRouterParams {
     method public int getDialogType();
-    method public boolean isMediaTransferReceiverDisabled();
+    method public boolean isMediaTransferReceiverEnabled();
     method public boolean isOutputSwitcherEnabled();
     method public boolean isTransferToLocalEnabled();
     field public static final int DIALOG_TYPE_DEFAULT = 1; // 0x1
@@ -465,7 +465,7 @@
     ctor public MediaRouterParams.Builder(androidx.mediarouter.media.MediaRouterParams);
     method public androidx.mediarouter.media.MediaRouterParams build();
     method public androidx.mediarouter.media.MediaRouterParams.Builder setDialogType(int);
-    method public androidx.mediarouter.media.MediaRouterParams.Builder setMediaTransferReceiverDisabled(boolean);
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setMediaTransferReceiverEnabled(boolean);
     method public androidx.mediarouter.media.MediaRouterParams.Builder setOutputSwitcherEnabled(boolean);
     method public androidx.mediarouter.media.MediaRouterParams.Builder setTransferToLocalEnabled(boolean);
   }
diff --git a/mediarouter/mediarouter/api/restricted_current.txt b/mediarouter/mediarouter/api/restricted_current.txt
index 6af2727..a750177 100644
--- a/mediarouter/mediarouter/api/restricted_current.txt
+++ b/mediarouter/mediarouter/api/restricted_current.txt
@@ -452,7 +452,7 @@
 
   public class MediaRouterParams {
     method public int getDialogType();
-    method public boolean isMediaTransferReceiverDisabled();
+    method public boolean isMediaTransferReceiverEnabled();
     method public boolean isOutputSwitcherEnabled();
     method public boolean isTransferToLocalEnabled();
     field public static final int DIALOG_TYPE_DEFAULT = 1; // 0x1
@@ -465,7 +465,7 @@
     ctor public MediaRouterParams.Builder(androidx.mediarouter.media.MediaRouterParams);
     method public androidx.mediarouter.media.MediaRouterParams build();
     method public androidx.mediarouter.media.MediaRouterParams.Builder setDialogType(int);
-    method public androidx.mediarouter.media.MediaRouterParams.Builder setMediaTransferReceiverDisabled(boolean);
+    method public androidx.mediarouter.media.MediaRouterParams.Builder setMediaTransferReceiverEnabled(boolean);
     method public androidx.mediarouter.media.MediaRouterParams.Builder setOutputSwitcherEnabled(boolean);
     method public androidx.mediarouter.media.MediaRouterParams.Builder setTransferToLocalEnabled(boolean);
   }
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
index aa9fff3..186093b 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouterTest.java
@@ -130,7 +130,7 @@
         final int dialogType = MediaRouterParams.DIALOG_TYPE_DYNAMIC_GROUP;
         final boolean isOutputSwitcherEnabled = true;
         final boolean transferToLocalEnabled = true;
-        final boolean transferReceiverDisabled = true;
+        final boolean transferReceiverEnabled = false;
         final Bundle extras = new Bundle();
         extras.putString(TEST_KEY, TEST_VALUE);
 
@@ -138,7 +138,7 @@
                 .setDialogType(dialogType)
                 .setOutputSwitcherEnabled(isOutputSwitcherEnabled)
                 .setTransferToLocalEnabled(transferToLocalEnabled)
-                .setMediaTransferReceiverDisabled(transferReceiverDisabled)
+                .setMediaTransferReceiverEnabled(transferReceiverEnabled)
                 .setExtras(extras)
                 .build();
 
@@ -147,13 +147,13 @@
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
             assertEquals(isOutputSwitcherEnabled, params.isOutputSwitcherEnabled());
             assertEquals(transferToLocalEnabled, params.isTransferToLocalEnabled());
-            assertEquals(transferReceiverDisabled, params.isMediaTransferReceiverDisabled());
+            assertEquals(transferReceiverEnabled, params.isMediaTransferReceiverEnabled());
         } else {
             // Earlier than Android R, output switcher cannot be enabled.
             // Same for transfer to local.
             assertFalse(params.isOutputSwitcherEnabled());
             assertFalse(params.isTransferToLocalEnabled());
-            assertFalse(params.isMediaTransferReceiverDisabled());
+            assertFalse(params.isMediaTransferReceiverEnabled());
         }
 
         extras.remove(TEST_KEY);
@@ -164,8 +164,8 @@
         assertEquals(params.getDialogType(), copiedParams.getDialogType());
         assertEquals(params.isOutputSwitcherEnabled(), copiedParams.isOutputSwitcherEnabled());
         assertEquals(params.isTransferToLocalEnabled(), copiedParams.isTransferToLocalEnabled());
-        assertEquals(params.isMediaTransferReceiverDisabled(),
-                copiedParams.isMediaTransferReceiverDisabled());
+        assertEquals(params.isMediaTransferReceiverEnabled(),
+                copiedParams.isMediaTransferReceiverEnabled());
         assertBundleEquals(params.getExtras(), copiedParams.getExtras());
     }
 
@@ -176,7 +176,7 @@
         final int dialogType = MediaRouterParams.DIALOG_TYPE_DYNAMIC_GROUP;
         final boolean isOutputSwitcherEnabled = true;
         final boolean transferToLocalEnabled = true;
-        final boolean transferReceiverDisabled = true;
+        final boolean transferReceiverEnabled = false;
         final Bundle paramExtras = new Bundle();
         paramExtras.putString(TEST_KEY, TEST_VALUE);
 
@@ -184,7 +184,7 @@
                 .setDialogType(dialogType)
                 .setOutputSwitcherEnabled(isOutputSwitcherEnabled)
                 .setTransferToLocalEnabled(transferToLocalEnabled)
-                .setMediaTransferReceiverDisabled(transferReceiverDisabled)
+                .setMediaTransferReceiverEnabled(transferReceiverEnabled)
                 .setExtras(paramExtras)
                 .build();
 
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index 6eb9f22..3334211 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -2654,6 +2654,8 @@
                     mMr2Provider = new MediaRoute2Provider(
                             mApplicationContext, new Mr2ProviderCallback());
                     addProvider(mMr2Provider);
+                    // Make sure mDiscoveryRequestForMr2Provider is updated
+                    updateDiscoveryRequest();
                     mRegisteredProviderWatcher.rescan();
                 }
 
@@ -2938,8 +2940,9 @@
         }
 
         boolean isMediaTransferEnabled() {
+            // The default value for isMediaTransferReceiverEnabled() is {@code true}.
             return mTransferReceiverDeclared
-                    && (mRouterParams == null || !mRouterParams.isMediaTransferReceiverDisabled());
+                    && (mRouterParams == null || mRouterParams.isMediaTransferReceiverEnabled());
         }
 
         boolean isTransferToLocalEnabled() {
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterParams.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterParams.java
index f580da1..405202f 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterParams.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterParams.java
@@ -76,14 +76,14 @@
 
     @DialogType
     final int mDialogType;
-    final boolean mMediaTransferReceiverDisabled;
+    final boolean mMediaTransferReceiverEnabled;
     final boolean mOutputSwitcherEnabled;
     final boolean mTransferToLocalEnabled;
     final Bundle mExtras;
 
     MediaRouterParams(@NonNull Builder builder) {
         mDialogType = builder.mDialogType;
-        mMediaTransferReceiverDisabled = builder.mMediaTransferDisabled;
+        mMediaTransferReceiverEnabled = builder.mMediaTransferEnabled;
         mOutputSwitcherEnabled = builder.mOutputSwitcherEnabled;
         mTransferToLocalEnabled = builder.mTransferToLocalEnabled;
 
@@ -101,12 +101,12 @@
     }
 
     /**
-     * Gets whether declared {@link MediaTransferReceiver} is disabled.
+     * Gets whether declared {@link MediaTransferReceiver} is enabled.
      *
-     * @see Builder#setMediaTransferReceiverDisabled(boolean)
+     * @see Builder#setMediaTransferReceiverEnabled(boolean)
      */
-    public boolean isMediaTransferReceiverDisabled() {
-        return mMediaTransferReceiverDisabled;
+    public boolean isMediaTransferReceiverEnabled() {
+        return mMediaTransferReceiverEnabled;
     }
 
     /**
@@ -146,7 +146,7 @@
     public static final class Builder {
         @DialogType
         int mDialogType = DIALOG_TYPE_DEFAULT;
-        boolean mMediaTransferDisabled;
+        boolean mMediaTransferEnabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
         boolean mOutputSwitcherEnabled;
         boolean mTransferToLocalEnabled;
         Bundle mExtras;
@@ -170,7 +170,7 @@
             mDialogType = params.mDialogType;
             mOutputSwitcherEnabled = params.mOutputSwitcherEnabled;
             mTransferToLocalEnabled = params.mTransferToLocalEnabled;
-            mMediaTransferDisabled = params.mMediaTransferReceiverDisabled;
+            mMediaTransferEnabled = params.mMediaTransferReceiverEnabled;
             mExtras = params.mExtras == null ? null : new Bundle(params.mExtras);
         }
 
@@ -194,12 +194,13 @@
         }
 
         /**
-         * Sets whether declared {@link MediaTransferReceiver} is disabled. This method will be
-         * no-op for Android versions earlier than Android R. The default value is {@code false}.
+         * Sets whether declared {@link MediaTransferReceiver} is enabled. The default value is
+         * {@code true}. This method will be no-op for Android versions earlier than Android R and
+         * it stays {@code false} on devices earlier than Android R.
          * <p>
-         * It is used to disable media transfer feature when {@link MediaTransferReceiver} is
+         * It can be used to disable media transfer feature when {@link MediaTransferReceiver} is
          * declared.
-         * If set to {@code true}, media transfer feature will be disabled
+         * If set to {@code false}, media transfer feature will be disabled
          * even when {@link MediaTransferReceiver} is declared.
          * <p>
          * It is not recommended to change this value at runtime.
@@ -207,9 +208,9 @@
          * @see MediaTransferReceiver
          */
         @NonNull
-        public Builder setMediaTransferReceiverDisabled(boolean disabled) {
+        public Builder setMediaTransferReceiverEnabled(boolean enabled) {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-                mMediaTransferDisabled = disabled;
+                mMediaTransferEnabled = enabled;
             }
             return this;
         }
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
index 59d3623..be66cde 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
@@ -714,6 +714,36 @@
     }
 
     @Test
+    fun testGetDialogViewModel() {
+        lateinit var navController: NavHostController
+        lateinit var model: TestViewModel
+
+        composeTestRule.setContent {
+            navController = rememberNavController()
+            NavHost(navController, first) {
+                composable(first) { }
+                dialog(second) {
+                    model = viewModel(it)
+                }
+            }
+        }
+
+        composeTestRule.runOnIdle {
+            navController.navigate(second)
+        }
+
+        composeTestRule.runOnIdle {
+            navController.popBackStack()
+        }
+
+        assertThat(model.wasCleared).isFalse()
+
+        composeTestRule.waitForIdle()
+
+        assertThat(model.wasCleared).isTrue()
+    }
+
+    @Test
     fun testGetGraphViewModelAfterRecompose() {
         lateinit var navController: NavHostController
         lateinit var model: TestViewModel
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/DialogHost.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/DialogHost.kt
index c8ef92c..700efea 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/DialogHost.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/DialogHost.kt
@@ -17,6 +17,7 @@
 package androidx.navigation.compose
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.saveable.rememberSaveableStateHolder
@@ -47,6 +48,12 @@
             backStackEntry.LocalOwnersProvider(saveableStateHolder) {
                 destination.content(backStackEntry)
             }
+
+            DisposableEffect(backStackEntry) {
+                onDispose {
+                    dialogNavigator.onTransitionComplete(backStackEntry)
+                }
+            }
         }
     }
 }
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/DialogNavigator.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/DialogNavigator.kt
index 4d433ec..bfac256 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/DialogNavigator.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/DialogNavigator.kt
@@ -61,7 +61,11 @@
     }
 
     override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
-        state.pop(popUpTo, savedState)
+        state.popWithTransition(popUpTo, savedState)
+    }
+
+    internal fun onTransitionComplete(entry: NavBackStackEntry) {
+        state.markTransitionComplete(entry)
     }
 
     /**
diff --git a/preference/OWNERS b/preference/OWNERS
index 50b530e..cc4dc56 100644
--- a/preference/OWNERS
+++ b/preference/OWNERS
@@ -1,2 +1 @@
-pavlis@google.com
 lpf@google.com
diff --git a/profileinstaller/integration-tests/init-macrobenchmark/src/androidTest/java/androidx/profileinstaller/integration/macrobenchmark/ProfileinstallerStartupBenchmark.kt b/profileinstaller/integration-tests/init-macrobenchmark/src/androidTest/java/androidx/profileinstaller/integration/macrobenchmark/ProfileinstallerStartupBenchmark.kt
index 83d0a926..4ece2c3 100644
--- a/profileinstaller/integration-tests/init-macrobenchmark/src/androidTest/java/androidx/profileinstaller/integration/macrobenchmark/ProfileinstallerStartupBenchmark.kt
+++ b/profileinstaller/integration-tests/init-macrobenchmark/src/androidTest/java/androidx/profileinstaller/integration/macrobenchmark/ProfileinstallerStartupBenchmark.kt
@@ -20,6 +20,7 @@
 import androidx.benchmark.macro.StartupMode
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 import androidx.test.filters.LargeTest
+import androidx.testutils.BASIC_COMPILATION_MODES
 import androidx.testutils.createStartupCompilationParams
 import androidx.testutils.measureStartup
 import org.junit.Rule
@@ -48,6 +49,8 @@
     companion object {
         @Parameterized.Parameters(name = "startup={0},compilation={1}")
         @JvmStatic
-        fun parameters() = createStartupCompilationParams()
+        fun parameters() = createStartupCompilationParams(
+            compilationModes = BASIC_COMPILATION_MODES
+        )
     }
 }
\ No newline at end of file
diff --git a/room/room-paging/build.gradle b/room/room-paging/build.gradle
index 42d90c5..b25cd0d 100644
--- a/room/room-paging/build.gradle
+++ b/room/room-paging/build.gradle
@@ -47,7 +47,7 @@
 
     implementation(project(":room:room-runtime"))
     implementation(project(":room:room-ktx"))
-    api("androidx.paging:paging-common:3.1.0-beta01")
+    api("androidx.paging:paging-common:3.1.0")
 
     androidTestImplementation(libs.kotlinCoroutinesTest)
     androidTestImplementation(libs.multidex)
diff --git a/room/room-runtime/build.gradle b/room/room-runtime/build.gradle
index 44afc76..48cdb86 100644
--- a/room/room-runtime/build.gradle
+++ b/room/room-runtime/build.gradle
@@ -34,8 +34,8 @@
 
 dependencies {
     api(project(":room:room-common"))
-    api("androidx.sqlite:sqlite-framework:2.2.0-beta01")
-    api("androidx.sqlite:sqlite:2.2.0-beta01")
+    api(project(":sqlite:sqlite-framework"))
+    api(project(":sqlite:sqlite"))
     implementation("androidx.arch.core:core-runtime:2.0.1")
     compileOnly("androidx.paging:paging-common:2.0.0")
     compileOnly("androidx.lifecycle:lifecycle-livedata-core:2.0.0")
diff --git a/room/room-testing/build.gradle b/room/room-testing/build.gradle
index d665ee7..1c0acde 100644
--- a/room/room-testing/build.gradle
+++ b/room/room-testing/build.gradle
@@ -25,8 +25,8 @@
 dependencies {
     api(project(":room:room-common"))
     api(project(":room:room-runtime"))
-    api("androidx.sqlite:sqlite:2.2.0-beta01")
-    api("androidx.sqlite:sqlite-framework:2.2.0-beta01")
+    api(project(":sqlite:sqlite"))
+    api(project(":sqlite:sqlite-framework"))
     api(project(":room:room-migration"))
     implementation("androidx.arch.core:core-runtime:2.0.1")
     api(libs.junit)
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java
index dc5253e..6f72b20 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/SampleMediaRouterActivity.java
@@ -756,7 +756,7 @@
         @Override
         public MediaRouterParams getRouterParams() {
             return new MediaRouterParams.Builder(super.getRouterParams())
-                    .setMediaTransferReceiverDisabled(true)
+                    .setMediaTransferReceiverEnabled(false)
                     .build();
         }
     }
diff --git a/samples/SupportPreferenceDemos/OWNERS b/samples/SupportPreferenceDemos/OWNERS
index f1b8c7e..b2c38b4 100644
--- a/samples/SupportPreferenceDemos/OWNERS
+++ b/samples/SupportPreferenceDemos/OWNERS
@@ -1,2 +1 @@
-pavlis@google.com
 lpf@google.com
\ No newline at end of file
diff --git a/slice/slice-view/src/main/res/values-it/strings.xml b/slice/slice-view/src/main/res/values-it/strings.xml
index 9087d7a..62c8052 100644
--- a/slice/slice-view/src/main/res/values-it/strings.xml
+++ b/slice/slice-view/src/main/res/values-it/strings.xml
@@ -22,16 +22,16 @@
     <string name="abc_slice_show_more" msgid="1112789899890391107">"Mostra altro"</string>
     <string name="abc_slice_updated" msgid="7932359091871934205">"Ultimo aggiornamento: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <plurals name="abc_slice_duration_min" formatted="false" msgid="7664017844210142826">
+      <item quantity="one"><xliff:g id="ID_2">%d</xliff:g> min fa</item>
       <item quantity="other"><xliff:g id="ID_2">%d</xliff:g> min fa</item>
-      <item quantity="one"><xliff:g id="ID_1">%d</xliff:g> min fa</item>
     </plurals>
     <plurals name="abc_slice_duration_years" formatted="false" msgid="2628491538787454021">
+      <item quantity="one"><xliff:g id="ID_2">%d</xliff:g> anno fa</item>
       <item quantity="other"><xliff:g id="ID_2">%d</xliff:g> anni fa</item>
-      <item quantity="one"><xliff:g id="ID_1">%d</xliff:g> anno fa</item>
     </plurals>
     <plurals name="abc_slice_duration_days" formatted="false" msgid="8356547162075064530">
+      <item quantity="one"><xliff:g id="ID_2">%d</xliff:g> giorno fa</item>
       <item quantity="other"><xliff:g id="ID_2">%d</xliff:g> giorni fa</item>
-      <item quantity="one"><xliff:g id="ID_1">%d</xliff:g> giorno fa</item>
     </plurals>
     <string name="abc_slice_error" msgid="1794214973158263497">"Impossibile collegarsi"</string>
 </resources>
diff --git a/slice/slice-view/src/main/res/values-pt-rPT/strings.xml b/slice/slice-view/src/main/res/values-pt-rPT/strings.xml
index 34a67ff..08c5c19 100644
--- a/slice/slice-view/src/main/res/values-pt-rPT/strings.xml
+++ b/slice/slice-view/src/main/res/values-pt-rPT/strings.xml
@@ -22,16 +22,16 @@
     <string name="abc_slice_show_more" msgid="1112789899890391107">"Mostrar mais"</string>
     <string name="abc_slice_updated" msgid="7932359091871934205">"Atualização: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <plurals name="abc_slice_duration_min" formatted="false" msgid="7664017844210142826">
-      <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> min</item>
       <item quantity="one">Há <xliff:g id="ID_1">%d</xliff:g> min</item>
+      <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> min</item>
     </plurals>
     <plurals name="abc_slice_duration_years" formatted="false" msgid="2628491538787454021">
-      <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> anos</item>
       <item quantity="one">Há <xliff:g id="ID_1">%d</xliff:g> ano</item>
+      <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> anos</item>
     </plurals>
     <plurals name="abc_slice_duration_days" formatted="false" msgid="8356547162075064530">
-      <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> dias</item>
       <item quantity="one">Há <xliff:g id="ID_1">%d</xliff:g> dia</item>
+      <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> dias</item>
     </plurals>
     <string name="abc_slice_error" msgid="1794214973158263497">"Não foi possível ligar"</string>
 </resources>
diff --git a/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
index ad1ce6b..42a98af 100644
--- a/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
+++ b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
@@ -27,14 +27,33 @@
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 
 /**
- * Default compilation modes to test for all AndroidX macrobenchmarks.
+ * Basic, always-usable compilation modes, when baseline profiles aren't available.
+ *
+ * Over time, it's expected very few macrobenchmarks will reference this directly, as more libraries
+ * gain baseline profiles.
  */
-val COMPILATION_MODES = listOf(
-    CompilationMode.None,
-    CompilationMode.Interpreted,
-    CompilationMode.BaselineProfile,
-    CompilationMode.SpeedProfile()
-)
+val BASIC_COMPILATION_MODES = if (Build.VERSION.SDK_INT < 24) {
+    // other modes aren't supported
+    listOf(CompilationMode.None)
+} else {
+    listOf(
+        CompilationMode.None,
+        CompilationMode.Interpreted,
+        CompilationMode.SpeedProfile()
+    )
+}
+
+/**
+ * Default compilation modes to test for all AndroidX macrobenchmarks.
+ *
+ * Baseline profiles are only supported from Nougat (API 24),
+ * currently through Android 11 (API 30)
+ */
+val COMPILATION_MODES = if (Build.VERSION.SDK_INT in 24..30) {
+    listOf(CompilationMode.BaselineProfile)
+} else {
+    emptyList()
+} + BASIC_COMPILATION_MODES
 
 /**
  * Temporary, while transitioning to new metrics
@@ -73,7 +92,11 @@
         StartupMode.HOT,
         StartupMode.WARM,
         StartupMode.COLD
-    ),
+    ).filter {
+        // skip StartupMode.HOT on Angler, API 23 - it works locally with same build on Bullhead,
+        // but not in Jetpack CI (b/204572406)
+        !(Build.VERSION.SDK_INT == 23 && it == StartupMode.HOT && Build.DEVICE == "angler")
+    },
     compilationModes: List<CompilationMode> = COMPILATION_MODES
 ): List<Array<Any>> = mutableListOf<Array<Any>>().apply {
     for (startupMode in startupModes) {
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TempListUtils.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TempListUtils.kt
index 2de2410..982d05c 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/TempListUtils.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TempListUtils.kt
@@ -24,6 +24,10 @@
 /**
  * Iterates through a [List] using the index and calls [action] for each item.
  * This does not allocate an iterator like [Iterable.forEach].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T> List<T>.fastForEach(action: (T) -> Unit) {
@@ -37,6 +41,10 @@
 /**
  * Applies the given [transform] function to each element of the original collection
  * and appends the results to the given [destination].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R, C : MutableCollection<in R>> List<T>.fastMapTo(
@@ -55,6 +63,10 @@
  * to each pair of two adjacent elements in this collection.
  *
  * The returned list is empty if this collection contains less than two elements.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
  */
 @OptIn(ExperimentalContracts::class)
 internal inline fun <T, R> List<T>.fastZipWithNext(transform: (T, T) -> R): List<R> {
diff --git a/wear/benchmark/integration-tests/macrobenchmark-target/build.gradle b/wear/benchmark/integration-tests/macrobenchmark-target/build.gradle
index 78ff9ef..8b165bb 100644
--- a/wear/benchmark/integration-tests/macrobenchmark-target/build.gradle
+++ b/wear/benchmark/integration-tests/macrobenchmark-target/build.gradle
@@ -40,5 +40,6 @@
     implementation projectOrArtifact(":activity:activity-ktx")
     implementation 'androidx.core:core-ktx'
     implementation(libs.material)
+    implementation(project(":profileinstaller:profileinstaller"))
     implementation 'androidx.wear:wear:1.1.0'
 }
diff --git a/wear/compose/compose-material/api/current.txt b/wear/compose/compose-material/api/current.txt
index a63d79c..b1cf747 100644
--- a/wear/compose/compose-material/api/current.txt
+++ b/wear/compose/compose-material/api/current.txt
@@ -145,15 +145,15 @@
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues getButtonsContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues getChipsContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues getConfirmationContentPadding();
-    method public long getIndefiniteDuration();
-    method public long getLongDuration();
-    method public long getShortDuration();
+    method public long getIndefiniteDurationMillis();
+    method public long getLongDurationMillis();
+    method public long getShortDurationMillis();
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.PaddingValues ButtonsContentPadding;
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.PaddingValues ChipsContentPadding;
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.PaddingValues ConfirmationContentPadding;
-    property public final long IndefiniteDuration;
-    property public final long LongDuration;
-    property public final long ShortDuration;
+    property public final long IndefiniteDurationMillis;
+    property public final long LongDurationMillis;
+    property public final long ShortDurationMillis;
     field public static final androidx.wear.compose.material.DialogDefaults INSTANCE;
   }
 
diff --git a/wear/compose/compose-material/api/public_plus_experimental_current.txt b/wear/compose/compose-material/api/public_plus_experimental_current.txt
index 8855f3c..c6e0939 100644
--- a/wear/compose/compose-material/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-material/api/public_plus_experimental_current.txt
@@ -145,15 +145,15 @@
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues getButtonsContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues getChipsContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues getConfirmationContentPadding();
-    method public long getIndefiniteDuration();
-    method public long getLongDuration();
-    method public long getShortDuration();
+    method public long getIndefiniteDurationMillis();
+    method public long getLongDurationMillis();
+    method public long getShortDurationMillis();
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.PaddingValues ButtonsContentPadding;
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.PaddingValues ChipsContentPadding;
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.PaddingValues ConfirmationContentPadding;
-    property public final long IndefiniteDuration;
-    property public final long LongDuration;
-    property public final long ShortDuration;
+    property public final long IndefiniteDurationMillis;
+    property public final long LongDurationMillis;
+    property public final long ShortDurationMillis;
     field public static final androidx.wear.compose.material.DialogDefaults INSTANCE;
   }
 
diff --git a/wear/compose/compose-material/api/restricted_current.txt b/wear/compose/compose-material/api/restricted_current.txt
index a63d79c..b1cf747 100644
--- a/wear/compose/compose-material/api/restricted_current.txt
+++ b/wear/compose/compose-material/api/restricted_current.txt
@@ -145,15 +145,15 @@
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues getButtonsContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues getChipsContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues getConfirmationContentPadding();
-    method public long getIndefiniteDuration();
-    method public long getLongDuration();
-    method public long getShortDuration();
+    method public long getIndefiniteDurationMillis();
+    method public long getLongDurationMillis();
+    method public long getShortDurationMillis();
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.PaddingValues ButtonsContentPadding;
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.PaddingValues ChipsContentPadding;
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.PaddingValues ConfirmationContentPadding;
-    property public final long IndefiniteDuration;
-    property public final long LongDuration;
-    property public final long ShortDuration;
+    property public final long IndefiniteDurationMillis;
+    property public final long LongDurationMillis;
+    property public final long ShortDurationMillis;
     field public static final androidx.wear.compose.material.DialogDefaults INSTANCE;
   }
 
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Dialog.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Dialog.kt
index 2916b1d..ec24d8e 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Dialog.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Dialog.kt
@@ -196,8 +196,8 @@
  * @param scrollState The scroll state for the dialog so that the scroll position can be displayed
  * e.g. by the [PositionIndicator] passed to [Scaffold].
  * @param durationMillis The number of milliseconds for which the dialog is displayed,
- * must be positive. Expected to be [DialogDefaults.ShortDuration], [DialogDefaults.LongDuration],
- * or [DialogDefaults.IndefiniteDuration].
+ * must be positive. Suggested values are [DialogDefaults.ShortDurationMillis],
+ * [DialogDefaults.LongDurationMillis] or [DialogDefaults.IndefiniteDurationMillis].
  * @param backgroundColor [Color] representing the background color for this dialog.
  * @param contentColor [Color] representing the content color for this dialog.
  * @param iconTintColor Icon [Color] that defaults to the [contentColor],
@@ -211,7 +211,7 @@
     modifier: Modifier = Modifier,
     icon: @Composable (() -> Unit)? = null,
     scrollState: ScrollState = rememberScrollState(),
-    durationMillis: Long = DialogDefaults.ShortDuration,
+    durationMillis: Long = DialogDefaults.ShortDurationMillis,
     backgroundColor: Color = MaterialTheme.colors.background,
     contentColor: Color = contentColorFor(backgroundColor),
     iconTintColor: Color = contentColor,
@@ -286,17 +286,17 @@
     /**
      * Short duration for showing [ConfirmationDialog].
      */
-    val ShortDuration = 4000L
+    val ShortDurationMillis = 4000L
 
     /**
      * Long duration for showing [ConfirmationDialog].
      */
-    val LongDuration = 10000L
+    val LongDurationMillis = 10000L
 
     /**
      * Show [ConfirmationDialog] indefinitely (supports swipe-to-dismiss).
      */
-    val IndefiniteDuration = Long.MAX_VALUE
+    val IndefiniteDurationMillis = Long.MAX_VALUE
 
     /**
      * Spacing between [Button]s.
diff --git a/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt b/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
index e874449..47863ba 100644
--- a/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
+++ b/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
@@ -583,6 +583,12 @@
                 onDestroyLatch.countDown()
                 backgroundHandlerThread.quitSafely()
             }
+
+            override fun setComplicationSlotConfigExtrasChangeCallback(
+                callback: WatchFace.ComplicationSlotConfigExtrasChangeCallback?
+            ) {
+                complicationSlotsManager.configExtrasChangeCallback = callback
+            }
         }
         if (!shouldTimeout) {
             WatchFace.registerEditorDelegate(watchComponentName, editorDelegate)
@@ -1440,6 +1446,57 @@
     }
 
     @Test
+    public fun mutable_configExtras() {
+        // Invoke the test data source chooser to record the result.
+        ComplicationHelperActivity.useTestComplicationDataSourceChooserActivity = true
+        // Invoke the data source chooser without checking for permissions first.
+        ComplicationHelperActivity.skipPermissionCheck = true
+
+        val chosenComplicationDataSourceInfo = ComplicationDataSourceInfo(
+            "TestDataSource3App",
+            "TestDataSource3",
+            Icon.createWithBitmap(
+                Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+            ),
+            ComplicationType.LONG_TEXT,
+            dataSource3
+        )
+        TestComplicationDataSourceChooserActivity.lastIntent = null
+        TestComplicationDataSourceChooserActivity.resultIntent = Intent().apply {
+            putExtra(
+                ComplicationDataSourceChooserIntent.EXTRA_PROVIDER_INFO,
+                chosenComplicationDataSourceInfo.toWireComplicationProviderInfo()
+            )
+        }
+
+        val scenario = createOnWatchFaceEditingTestActivity(
+            emptyList(),
+            listOf(leftComplication, rightComplication)
+        )
+
+        lateinit var editorSession: EditorSession
+        scenario.onActivity { activity ->
+            editorSession = activity.editorSession
+        }
+
+        runBlocking {
+            rightComplication.configExtras = Bundle().apply {
+                putString(PROVIDER_CHOOSER_EXTRA_KEY, "Updated")
+            }
+
+            val chosenComplicationDataSource =
+                editorSession.openComplicationDataSourceChooser(RIGHT_COMPLICATION_ID)
+            assertThat(chosenComplicationDataSource).isNotNull()
+
+            assertThat(
+                TestComplicationDataSourceChooserActivity.lastIntent?.extras?.getString(
+                    PROVIDER_CHOOSER_EXTRA_KEY
+                )
+            ).isEqualTo("Updated")
+        }
+    }
+
+    @Test
     public fun getComplicationIdAt() {
         val scenario = createOnWatchFaceEditingTestActivity(
             emptyList(),
diff --git a/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt b/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
index ab7bdf8..8690ac7 100644
--- a/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
+++ b/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
@@ -889,6 +889,7 @@
 
         // Note this has to be done last to ensure tests are not racy.
         if (this::editorDelegate.isInitialized) {
+            editorDelegate.setComplicationSlotConfigExtrasChangeCallback(null)
             editorDelegate.onDestroy()
         }
     }
@@ -910,6 +911,14 @@
         )
 
         fetchComplicationsDataJob = fetchComplicationsData(backgroundCoroutineScope)
+
+        editorDelegate.setComplicationSlotConfigExtrasChangeCallback(
+            object : WatchFace.ComplicationSlotConfigExtrasChangeCallback {
+                override fun onComplicationSlotConfigExtrasChanged() {
+                    maybeUpdateComplicationSlotsState()
+                }
+            }
+        )
     }
 
     override val showComplicationDeniedDialogIntent
diff --git a/wear/watchface/watchface/api/current.txt b/wear/watchface/watchface/api/current.txt
index a8af996..f442dc5 100644
--- a/wear/watchface/watchface/api/current.txt
+++ b/wear/watchface/watchface/api/current.txt
@@ -45,6 +45,7 @@
     method @UiThread public boolean isEnabled();
     method @UiThread public void render(android.graphics.Canvas canvas, java.time.ZonedDateTime zonedDateTime, androidx.wear.watchface.RenderParameters renderParameters);
     method @UiThread public void renderHighlightLayer(android.graphics.Canvas canvas, java.time.ZonedDateTime zonedDateTime, androidx.wear.watchface.RenderParameters renderParameters);
+    method public void setConfigExtras(android.os.Bundle value);
     property @UiThread public final int accessibilityTraversalIndex;
     property public final int boundsType;
     property public final androidx.wear.watchface.CanvasComplicationFactory canvasComplicationFactory;
diff --git a/wear/watchface/watchface/api/public_plus_experimental_current.txt b/wear/watchface/watchface/api/public_plus_experimental_current.txt
index a8af996..f442dc5 100644
--- a/wear/watchface/watchface/api/public_plus_experimental_current.txt
+++ b/wear/watchface/watchface/api/public_plus_experimental_current.txt
@@ -45,6 +45,7 @@
     method @UiThread public boolean isEnabled();
     method @UiThread public void render(android.graphics.Canvas canvas, java.time.ZonedDateTime zonedDateTime, androidx.wear.watchface.RenderParameters renderParameters);
     method @UiThread public void renderHighlightLayer(android.graphics.Canvas canvas, java.time.ZonedDateTime zonedDateTime, androidx.wear.watchface.RenderParameters renderParameters);
+    method public void setConfigExtras(android.os.Bundle value);
     property @UiThread public final int accessibilityTraversalIndex;
     property public final int boundsType;
     property public final androidx.wear.watchface.CanvasComplicationFactory canvasComplicationFactory;
diff --git a/wear/watchface/watchface/api/restricted_current.txt b/wear/watchface/watchface/api/restricted_current.txt
index 958905b..50cb2dc 100644
--- a/wear/watchface/watchface/api/restricted_current.txt
+++ b/wear/watchface/watchface/api/restricted_current.txt
@@ -45,6 +45,7 @@
     method @UiThread public boolean isEnabled();
     method @UiThread public void render(android.graphics.Canvas canvas, java.time.ZonedDateTime zonedDateTime, androidx.wear.watchface.RenderParameters renderParameters);
     method @UiThread public void renderHighlightLayer(android.graphics.Canvas canvas, java.time.ZonedDateTime zonedDateTime, androidx.wear.watchface.RenderParameters renderParameters);
+    method public void setConfigExtras(android.os.Bundle value);
     property @UiThread public final int accessibilityTraversalIndex;
     property public final int boundsType;
     property public final androidx.wear.watchface.CanvasComplicationFactory canvasComplicationFactory;
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
index 8e16e8c..7d4df5a 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
@@ -208,7 +208,8 @@
  * Editors need to know the initial state of a complication slot to predict the effects of making a
  * style change.
  * @param configExtras Extras to be merged into the Intent sent when invoking the complication data
- * source chooser activity.
+ * source chooser activity. This features is intended for OEM watch faces where they have elements
+ * that behave like a complication but are in fact entirely watch face specific.
  * @param fixedComplicationDataSource  Whether or not the complication data source is fixed (i.e.
  * can't be changed by the user).  This is useful for watch faces built around specific
  * complications.
@@ -226,7 +227,7 @@
     defaultDataSourceType: ComplicationType,
     @get:JvmName("isInitiallyEnabled")
     public val initiallyEnabled: Boolean,
-    public val configExtras: Bundle,
+    configExtras: Bundle,
     @get:JvmName("isFixedComplicationDataSource")
     public val fixedComplicationDataSource: Boolean,
     public val tapFilter: ComplicationTapFilter
@@ -238,6 +239,17 @@
     internal lateinit var complicationSlotsManager: ComplicationSlotsManager
 
     /**
+     * Extras to be merged into the Intent sent when invoking the complication data source chooser
+     * activity.
+     */
+    public var configExtras: Bundle = configExtras
+        set(value) {
+            field = value
+            complicationSlotsManager.configExtrasChangeCallback
+                ?.onComplicationSlotConfigExtrasChanged()
+        }
+
+    /**
      * The [CanvasComplication] used to render the complication. This can't be used until after
      * [WatchFaceService.createWatchFace] has completed.
      */
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
index 5def43e..5da65c6 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
@@ -118,6 +118,11 @@
 
     private val complicationListeners = HashSet<TapCallback>()
 
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public var configExtrasChangeCallback: WatchFace.ComplicationSlotConfigExtrasChangeCallback? =
+        null
+
     @VisibleForTesting
     internal constructor(
         complicationSlotCollection: Collection<ComplicationSlot>,
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index d8e9b4e..91b47c0 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -254,6 +254,21 @@
 
         /** Signals that the activity is going away and resources should be released. */
         public fun onDestroy()
+
+        /** Sets a callback to observe an y changes to [ComplicationSlot.configExtras]. */
+        public fun setComplicationSlotConfigExtrasChangeCallback(
+            callback: ComplicationSlotConfigExtrasChangeCallback?
+        )
+    }
+
+    /**
+     * Used to inform EditorSession about changes to [ComplicationSlot.configExtras].
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public interface ComplicationSlotConfigExtrasChangeCallback {
+      public fun onComplicationSlotConfigExtrasChanged()
     }
 
     /**
@@ -758,6 +773,12 @@
             return screenShot
         }
 
+        override fun setComplicationSlotConfigExtrasChangeCallback(
+            callback: WatchFace.ComplicationSlotConfigExtrasChangeCallback?
+        ) {
+            complicationSlotsManager.configExtrasChangeCallback = callback
+        }
+
         override fun onDestroy(): Unit = TraceEvent("WFEditorDelegate.onDestroy").use {
             if (watchState.isHeadless) {
                 this@WatchFaceImpl.onDestroy()
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
index 362edee..8a207d8 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
@@ -170,6 +170,8 @@
     }
 
     fun onDestroy() {
-        engine = null
+        uiThreadCoroutineScope.launch {
+            engine = null
+        }
     }
 }