Merge "Change transitions in progress back to a Set" into androidx-main
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedCapabilities.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedCapabilities.java
new file mode 100644
index 0000000..34c4084
--- /dev/null
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedCapabilities.java
@@ -0,0 +1,34 @@
+/*
+ * 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.appsearch.localstorage;
+
+import androidx.annotation.RestrictTo;
+import androidx.appsearch.app.Capabilities;
+
+/**
+ * An implementation of {@link Capabilities}. This implementation always returns true. This is
+ * sufficient for the use in the local backend because all features are always available on the
+ * local backend.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class AlwaysSupportedCapabilities implements Capabilities {
+
+    @Override
+    public boolean isSubmatchSupported() {
+        return true;
+    }
+}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/GlobalSearchSessionImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/GlobalSearchSessionImpl.java
index 23c07925..3406886 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/GlobalSearchSessionImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/GlobalSearchSessionImpl.java
@@ -21,6 +21,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appsearch.app.AppSearchResult;
+import androidx.appsearch.app.Capabilities;
 import androidx.appsearch.app.GlobalSearchSession;
 import androidx.appsearch.app.ReportSystemUsageRequest;
 import androidx.appsearch.app.SearchResults;
@@ -43,6 +44,7 @@
 class GlobalSearchSessionImpl implements GlobalSearchSession {
     private final AppSearchImpl mAppSearchImpl;
     private final Executor mExecutor;
+    private final Capabilities mCapabilities;
     private final Context mContext;
 
     private boolean mIsClosed = false;
@@ -53,10 +55,12 @@
     GlobalSearchSessionImpl(
             @NonNull AppSearchImpl appSearchImpl,
             @NonNull Executor executor,
+            @NonNull Capabilities capabilities,
             @NonNull Context context,
             @Nullable AppSearchLogger logger) {
         mAppSearchImpl = Preconditions.checkNotNull(appSearchImpl);
         mExecutor = Preconditions.checkNotNull(executor);
+        mCapabilities = Preconditions.checkNotNull(capabilities);
         mContext = Preconditions.checkNotNull(context);
         mLogger = logger;
     }
@@ -96,6 +100,12 @@
         });
     }
 
+    @NonNull
+    @Override
+    public Capabilities getCapabilities() {
+        return mCapabilities;
+    }
+
     @Override
     public void close() {
         mIsClosed = true;
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
index b09aba0..1bdb76b 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
@@ -381,6 +381,7 @@
         return new SearchSessionImpl(
                 mAppSearchImpl,
                 context.mExecutor,
+                new AlwaysSupportedCapabilities(),
                 context.mContext.getPackageName(),
                 context.mDatabaseName,
                 context.mLogger);
@@ -389,7 +390,7 @@
     @NonNull
     private GlobalSearchSession doCreateGlobalSearchSession(
             @NonNull GlobalSearchContext context) {
-        return new GlobalSearchSessionImpl(mAppSearchImpl, context.mExecutor, context.mContext,
-                context.mLogger);
+        return new GlobalSearchSessionImpl(mAppSearchImpl, context.mExecutor,
+                new AlwaysSupportedCapabilities(), context.mContext, context.mLogger);
     }
 }
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
index f2b98e5..ebdfe96 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/SearchSessionImpl.java
@@ -25,6 +25,7 @@
 import androidx.annotation.Nullable;
 import androidx.appsearch.app.AppSearchBatchResult;
 import androidx.appsearch.app.AppSearchSession;
+import androidx.appsearch.app.Capabilities;
 import androidx.appsearch.app.GenericDocument;
 import androidx.appsearch.app.GetByDocumentIdRequest;
 import androidx.appsearch.app.GetSchemaResponse;
@@ -70,6 +71,7 @@
     private static final String TAG = "AppSearchSessionImpl";
     private final AppSearchImpl mAppSearchImpl;
     private final Executor mExecutor;
+    private final Capabilities mCapabilities;
     private final String mPackageName;
     private final String mDatabaseName;
     private volatile boolean mIsMutated = false;
@@ -79,11 +81,13 @@
     SearchSessionImpl(
             @NonNull AppSearchImpl appSearchImpl,
             @NonNull Executor executor,
+            @NonNull Capabilities capabilities,
             @NonNull String packageName,
             @NonNull String databaseName,
             @Nullable AppSearchLogger logger) {
         mAppSearchImpl = Preconditions.checkNotNull(appSearchImpl);
         mExecutor = Preconditions.checkNotNull(executor);
+        mCapabilities = Preconditions.checkNotNull(capabilities);
         mPackageName = packageName;
         mDatabaseName = Preconditions.checkNotNull(databaseName);
         mLogger = logger;
@@ -433,6 +437,12 @@
         });
     }
 
+    @NonNull
+    @Override
+    public Capabilities getCapabilities() {
+        return mCapabilities;
+    }
+
     @Override
     @SuppressWarnings("FutureReturnValueIgnored")
     public void close() {
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/CapabilitiesImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/CapabilitiesImpl.java
new file mode 100644
index 0000000..4d21fbad
--- /dev/null
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/CapabilitiesImpl.java
@@ -0,0 +1,32 @@
+/*
+ * 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.appsearch.platformstorage;
+
+import androidx.appsearch.app.Capabilities;
+
+/**
+ * An implementation of {@link Capabilities}. Feature availability is dependent on Android API
+ * level.
+ */
+final class CapabilitiesImpl implements Capabilities {
+
+    @Override
+    public boolean isSubmatchSupported() {
+        // TODO(b/201316758) : Update to reflect support in Android T+ once this feature is synced
+        // over into service-appsearch.
+        return false;
+    }
+}
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/GlobalSearchSessionImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/GlobalSearchSessionImpl.java
index b5b7cc0..102cf91 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/GlobalSearchSessionImpl.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/GlobalSearchSessionImpl.java
@@ -20,6 +20,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
+import androidx.appsearch.app.Capabilities;
 import androidx.appsearch.app.GlobalSearchSession;
 import androidx.appsearch.app.ReportSystemUsageRequest;
 import androidx.appsearch.app.SearchResults;
@@ -44,12 +45,15 @@
 class GlobalSearchSessionImpl implements GlobalSearchSession {
     private final android.app.appsearch.GlobalSearchSession mPlatformSession;
     private final Executor mExecutor;
+    private final Capabilities mCapabilities;
 
     GlobalSearchSessionImpl(
             @NonNull android.app.appsearch.GlobalSearchSession platformSession,
-            @NonNull Executor executor) {
+            @NonNull Executor executor,
+            @NonNull Capabilities capabilities) {
         mPlatformSession = Preconditions.checkNotNull(platformSession);
         mExecutor = Preconditions.checkNotNull(executor);
+        mCapabilities = Preconditions.checkNotNull(capabilities);
     }
 
     @Override
@@ -79,6 +83,12 @@
         return future;
     }
 
+    @NonNull
+    @Override
+    public Capabilities getCapabilities() {
+        return mCapabilities;
+    }
+
     @Override
     public void close() {
         mPlatformSession.close();
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/PlatformStorage.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/PlatformStorage.java
index b07782b..9cd4b06 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/PlatformStorage.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/PlatformStorage.java
@@ -221,7 +221,8 @@
                 result -> {
                     if (result.isSuccess()) {
                         future.set(
-                                new SearchSessionImpl(result.getResultValue(), context.mExecutor));
+                                new SearchSessionImpl(result.getResultValue(), context.mExecutor,
+                                new CapabilitiesImpl()));
                     } else {
                         future.setException(
                                 new AppSearchException(
@@ -246,7 +247,8 @@
                 result -> {
                     if (result.isSuccess()) {
                         future.set(new GlobalSearchSessionImpl(
-                                result.getResultValue(), context.mExecutor));
+                                result.getResultValue(), context.mExecutor,
+                                new CapabilitiesImpl()));
                     } else {
                         future.setException(
                                 new AppSearchException(
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/SearchSessionImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/SearchSessionImpl.java
index 14484c0..2eb8162 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/SearchSessionImpl.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/SearchSessionImpl.java
@@ -22,6 +22,7 @@
 import androidx.annotation.RestrictTo;
 import androidx.appsearch.app.AppSearchBatchResult;
 import androidx.appsearch.app.AppSearchSession;
+import androidx.appsearch.app.Capabilities;
 import androidx.appsearch.app.GenericDocument;
 import androidx.appsearch.app.GetByDocumentIdRequest;
 import androidx.appsearch.app.GetSchemaResponse;
@@ -60,12 +61,15 @@
 class SearchSessionImpl implements AppSearchSession {
     private final android.app.appsearch.AppSearchSession mPlatformSession;
     private final Executor mExecutor;
+    private final Capabilities mCapabilities;
 
     SearchSessionImpl(
             @NonNull android.app.appsearch.AppSearchSession platformSession,
-            @NonNull Executor executor) {
+            @NonNull Executor executor,
+            @NonNull Capabilities capabilities) {
         mPlatformSession = Preconditions.checkNotNull(platformSession);
         mExecutor = Preconditions.checkNotNull(executor);
+        mCapabilities = Preconditions.checkNotNull(capabilities);
     }
 
     @Override
@@ -245,6 +249,12 @@
         return future;
     }
 
+    @NonNull
+    @Override
+    public Capabilities getCapabilities() {
+        return mCapabilities;
+    }
+
     @Override
     public void close() {
         mPlatformSession.close();
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchResultToPlatformConverter.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchResultToPlatformConverter.java
index fc7b70e..841cbe7 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchResultToPlatformConverter.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchResultToPlatformConverter.java
@@ -43,8 +43,8 @@
         Preconditions.checkNotNull(platformResult);
         GenericDocument document = GenericDocumentToPlatformConverter.toJetpackGenericDocument(
                 platformResult.getGenericDocument());
-        SearchResult.Builder builder = new SearchResult.Builder(
-                platformResult.getPackageName(), platformResult.getDatabaseName())
+        SearchResult.Builder builder = new SearchResult.Builder(platformResult.getPackageName(),
+                platformResult.getDatabaseName())
                 .setGenericDocument(document)
                 .setRankingSignal(platformResult.getRankingSignal());
         List<android.app.appsearch.SearchResult.MatchInfo> platformMatches =
@@ -60,6 +60,8 @@
     private static SearchResult.MatchInfo toJetpackMatchInfo(
             @NonNull android.app.appsearch.SearchResult.MatchInfo platformMatchInfo) {
         Preconditions.checkNotNull(platformMatchInfo);
+        // TODO(b/201316758) : Copy over submatch range info once it is added to
+        //  framework-appsearch.
         return new SearchResult.MatchInfo.Builder(platformMatchInfo.getPropertyPath())
                 .setExactMatchRange(
                         new SearchResult.MatchRange(
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index d3583ab..c375403 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -178,6 +178,7 @@
   public interface AppSearchSession extends java.io.Closeable {
     method public void close();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentId(androidx.appsearch.app.GetByDocumentIdRequest);
+    method public androidx.appsearch.app.Capabilities getCapabilities();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespaces();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchema();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfo();
@@ -190,6 +191,10 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchema(androidx.appsearch.app.SetSchemaRequest);
   }
 
+  public interface Capabilities {
+    method public boolean isSubmatchSupported();
+  }
+
   public interface DocumentClassFactory<T> {
     method public T fromGenericDocument(androidx.appsearch.app.GenericDocument) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.AppSearchSchema getSchema() throws androidx.appsearch.exceptions.AppSearchException;
@@ -272,6 +277,7 @@
 
   public interface GlobalSearchSession extends java.io.Closeable {
     method public void close();
+    method public androidx.appsearch.app.Capabilities getCapabilities();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsage(androidx.appsearch.app.ReportSystemUsageRequest);
     method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
   }
diff --git a/appsearch/appsearch/api/public_plus_experimental_current.txt b/appsearch/appsearch/api/public_plus_experimental_current.txt
index d3583ab..c375403 100644
--- a/appsearch/appsearch/api/public_plus_experimental_current.txt
+++ b/appsearch/appsearch/api/public_plus_experimental_current.txt
@@ -178,6 +178,7 @@
   public interface AppSearchSession extends java.io.Closeable {
     method public void close();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentId(androidx.appsearch.app.GetByDocumentIdRequest);
+    method public androidx.appsearch.app.Capabilities getCapabilities();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespaces();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchema();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfo();
@@ -190,6 +191,10 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchema(androidx.appsearch.app.SetSchemaRequest);
   }
 
+  public interface Capabilities {
+    method public boolean isSubmatchSupported();
+  }
+
   public interface DocumentClassFactory<T> {
     method public T fromGenericDocument(androidx.appsearch.app.GenericDocument) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.AppSearchSchema getSchema() throws androidx.appsearch.exceptions.AppSearchException;
@@ -272,6 +277,7 @@
 
   public interface GlobalSearchSession extends java.io.Closeable {
     method public void close();
+    method public androidx.appsearch.app.Capabilities getCapabilities();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsage(androidx.appsearch.app.ReportSystemUsageRequest);
     method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
   }
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index d3583ab..c375403 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -178,6 +178,7 @@
   public interface AppSearchSession extends java.io.Closeable {
     method public void close();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentId(androidx.appsearch.app.GetByDocumentIdRequest);
+    method public androidx.appsearch.app.Capabilities getCapabilities();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespaces();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchema();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfo();
@@ -190,6 +191,10 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchema(androidx.appsearch.app.SetSchemaRequest);
   }
 
+  public interface Capabilities {
+    method public boolean isSubmatchSupported();
+  }
+
   public interface DocumentClassFactory<T> {
     method public T fromGenericDocument(androidx.appsearch.app.GenericDocument) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.AppSearchSchema getSchema() throws androidx.appsearch.exceptions.AppSearchException;
@@ -272,6 +277,7 @@
 
   public interface GlobalSearchSession extends java.io.Closeable {
     method public void close();
+    method public androidx.appsearch.app.Capabilities getCapabilities();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsage(androidx.appsearch.app.ReportSystemUsageRequest);
     method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
   }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
index 424b25a..6441295 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
@@ -16,8 +16,6 @@
 
 package androidx.appsearch.cts.app;
 
-import static android.os.Build.VERSION_CODES;
-
 import static androidx.appsearch.app.AppSearchResult.RESULT_INVALID_SCHEMA;
 import static androidx.appsearch.app.AppSearchResult.RESULT_NOT_FOUND;
 import static androidx.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
@@ -83,9 +81,6 @@
     protected abstract ListenableFuture<AppSearchSession> createSearchSession(
             @NonNull String dbName, @NonNull ExecutorService executor);
 
-    // Returns the Android version that the current instance of AppSearchSession is based on.
-    protected abstract int getAppSearchApiTarget();
-
     @Before
     public void setUp() throws Exception {
         Context context = ApplicationProvider.getApplicationContext();
@@ -1906,13 +1901,14 @@
                 new SearchResult.MatchRange(/*lower=*/26,  /*upper=*/33));
         assertThat(matchInfo.getSnippet()).isEqualTo("is foo.");
 
-        if (getAppSearchApiTarget() <= VERSION_CODES.S) {
-            // Submatch is not support on any backend that targets a platform S or lower.
-            assertThat(matchInfo.getSubmatchRange()).isEqualTo(
-                    new SearchResult.MatchRange(/*lower=*/0,  /*upper=*/0));
-            assertThat(matchInfo.getSubmatch().length()).isEqualTo(0);
+        if (!mDb1.getCapabilities().isSubmatchSupported()) {
+            assertThrows(
+                    UnsupportedOperationException.class,
+                    () -> matchInfo.getSubmatchRange());
+            assertThrows(
+                    UnsupportedOperationException.class,
+                    () -> matchInfo.getSubmatch());
         } else {
-            // Submatch is enabled on any backend that targets a platform beyond S.
             assertThat(matchInfo.getSubmatchRange()).isEqualTo(
                     new SearchResult.MatchRange(/*lower=*/29,  /*upper=*/31));
             assertThat(matchInfo.getSubmatch()).isEqualTo("fo");
@@ -2040,13 +2036,14 @@
                 new SearchResult.MatchRange(/*lower=*/44,  /*upper=*/45));
         assertThat(matchInfo.getExactMatch()).isEqualTo("は");
 
-        if (getAppSearchApiTarget() <= VERSION_CODES.S) {
-            // Submatch is not support on any backend that targets a platform S or lower.
-            assertThat(matchInfo.getSubmatchRange()).isEqualTo(
-                    new SearchResult.MatchRange(/*lower=*/0,  /*upper=*/0));
-            assertThat(matchInfo.getSubmatch().length()).isEqualTo(0);
+        if (!mDb1.getCapabilities().isSubmatchSupported()) {
+            assertThrows(
+                    UnsupportedOperationException.class,
+                    () -> matchInfo.getSubmatchRange());
+            assertThrows(
+                    UnsupportedOperationException.class,
+                    () -> matchInfo.getSubmatch());
         } else {
-            // Submatch is enabled on any backend that targets a platform beyond S.
             assertThat(matchInfo.getSubmatchRange()).isEqualTo(
                     new SearchResult.MatchRange(/*lower=*/44,  /*upper=*/45));
             assertThat(matchInfo.getSubmatch()).isEqualTo("は");
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionLocalCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionLocalCtsTest.java
index f675483..f987118 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionLocalCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionLocalCtsTest.java
@@ -67,12 +67,6 @@
                         .setWorkerExecutor(executor).build());
     }
 
-    @Override
-    protected int getAppSearchApiTarget() {
-        // Any feature available in AppSearchSession is always available in the local backend.
-        return Integer.MAX_VALUE;
-    }
-
     // TODO(b/194207451) This test can be moved to CtsTestBase if customized logger is
     //  supported for platform backend.
     @Test
@@ -493,4 +487,13 @@
         assertThat(result.getFailures().get("id1").getErrorMessage())
                 .contains("was too large to write. Max is 16777215");
     }
+
+    @Test
+    public void testCapabilities() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+        AppSearchSession db2 = LocalStorage.createSearchSession(
+                new LocalStorage.SearchContext.Builder(context, DB_NAME_2).build()).get();
+
+        assertThat(db2.getCapabilities().isSubmatchSupported()).isTrue();
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionPlatformCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionPlatformCtsTest.java
index e3f4770..2490a03 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionPlatformCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionPlatformCtsTest.java
@@ -16,7 +16,7 @@
 // @exportToFramework:skipFile()
 package androidx.appsearch.cts.app;
 
-import static android.os.Build.VERSION;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
 import android.os.Build;
@@ -29,6 +29,8 @@
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import org.junit.Test;
+
 import java.util.concurrent.ExecutorService;
 
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
@@ -49,9 +51,14 @@
                         .setWorkerExecutor(executor).build());
     }
 
-    @Override
-    protected int getAppSearchApiTarget() {
-        // Feature availability in the platform backend depends on the device's API level.
-        return VERSION.SDK_INT;
+    @Test
+    public void testCapabilities() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+        AppSearchSession db2 = PlatformStorage.createSearchSession(
+                new PlatformStorage.SearchContext.Builder(context, DB_NAME_2).build()).get();
+
+        // TODO(b/201316758) Update to reflect support in Android T+ once this feature is synced
+        // over into service-appsearch.
+        assertThat(db2.getCapabilities().isSubmatchSupported()).isFalse();
     }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java
index 2db5a7a..575a37b 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
 import androidx.appsearch.app.SearchResult;
 import androidx.appsearch.testutil.AppSearchEmail;
 
@@ -67,4 +69,38 @@
         assertThat(matchRange.getStart()).isEqualTo(13);
         assertThat(matchRange.getEnd()).isEqualTo(47);
     }
+
+    @Test
+    public void testSubmatchRangeNotSet() {
+        AppSearchEmail email = new AppSearchEmail.Builder("namespace1", "id1")
+                .setBody("Hello World.")
+                .build();
+        SearchResult.MatchInfo matchInfo =
+                new SearchResult.MatchInfo.Builder("body").build();
+        SearchResult searchResult = new SearchResult.Builder("packageName", "databaseName")
+                .setGenericDocument(email)
+                .addMatchInfo(matchInfo)
+                .build();
+
+        // When submatch isn't set, calling getSubmatch and getSubmatchRange should throw.
+        final SearchResult.MatchInfo actualMatchInfoNoSubmatch =
+                searchResult.getMatchInfos().get(0);
+        assertThrows(UnsupportedOperationException.class,
+                () -> actualMatchInfoNoSubmatch.getSubmatch());
+        assertThrows(UnsupportedOperationException.class,
+                () -> actualMatchInfoNoSubmatch.getSubmatchRange());
+
+        // When submatch is set, calling getSubmatch and getSubmatchRange should return the
+        // submatch without any problems.
+        SearchResult.MatchRange submatchRange = new SearchResult.MatchRange(3, 5);
+        matchInfo = new SearchResult.MatchInfo.Builder("body").setSubmatchRange(
+                submatchRange).build();
+        searchResult = new SearchResult.Builder("packageName", "databaseName")
+                .setGenericDocument(email)
+                .addMatchInfo(matchInfo)
+                .build();
+        final SearchResult.MatchInfo actualMatchInfo = searchResult.getMatchInfos().get(0);
+        assertThat(actualMatchInfo.getSubmatch()).isEqualTo("lo");
+        assertThat(actualMatchInfo.getSubmatchRange()).isEqualTo(submatchRange);
+    }
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
index 4c4c8c8..c1279d39 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
@@ -262,6 +262,12 @@
     ListenableFuture<Void> requestFlush();
 
     /**
+     * Returns the {@link Capabilities} to check for the availability of certain features
+     * for this session.
+     */
+    @NonNull Capabilities getCapabilities();
+
+    /**
      * Closes the {@link AppSearchSession} to persist all schema and document updates, additions,
      * and deletes to disk.
      */
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Capabilities.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Capabilities.java
new file mode 100644
index 0000000..fb5d8aa
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Capabilities.java
@@ -0,0 +1,29 @@
+/*
+ * 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.appsearch.app;
+
+/**
+ * A class that encapsulates all features that are only supported with certain combinations of
+ * backend and Android API Level.
+ */
+public interface Capabilities {
+
+    /**
+     * Returns whether or not {@link SearchResult.MatchInfo#getSubmatchRange} and
+     * {@link SearchResult.MatchInfo#getSubmatch} are available.
+     */
+    boolean isSubmatchSupported();
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GlobalSearchSession.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GlobalSearchSession.java
index 8b7dbe9..1d8f921 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GlobalSearchSession.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GlobalSearchSession.java
@@ -77,6 +77,12 @@
     @NonNull
     ListenableFuture<Void> reportSystemUsage(@NonNull ReportSystemUsageRequest request);
 
+    /**
+     * Returns the {@link Capabilities} to check for the availability of certain features
+     * for this session.
+     */
+    @NonNull Capabilities getCapabilities();
+
     /** Closes the {@link GlobalSearchSession}. */
     @Override
     void close();
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResult.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResult.java
index 556c9b0..150cc56 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResult.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResult.java
@@ -423,9 +423,16 @@
          * <p>Class example 1: this returns [29, 32].
          * <p>Class example 2: for the first {@link MatchInfo}, this returns [0, 4] and, for the
          * second {@link MatchInfo}, this returns [0, 4].
+         *
+         * <p>This information may not be available depending on the backend and Android API
+         * level. To ensure it is available, call {@link Capabilities#isSubmatchSupported}.
+         *
+         * @throws UnsupportedOperationException if {@link Capabilities#isSubmatchSupported} is
+         * false.
          */
         @NonNull
         public MatchRange getSubmatchRange() {
+            checkSubmatchSupported();
             if (mSubmatchRange == null) {
                 mSubmatchRange = new MatchRange(
                         mBundle.getInt(SUBMATCH_RANGE_LOWER_FIELD),
@@ -439,9 +446,16 @@
          * <p>Class example 1: this returns "foo".
          * <p>Class example 2: for the first {@link MatchInfo}, this returns "Test" and, for the
          * second {@link MatchInfo}, this returns "Test".
+         *
+         * This information may not be available depending on the backend and Android API level. To
+         * ensure it is available, call {@link Capabilities#isSubmatchSupported}.
+         *
+         * @throws UnsupportedOperationException if {@link Capabilities#isSubmatchSupported} is
+         * false.
          */
         @NonNull
         public CharSequence getSubmatch() {
+            checkSubmatchSupported();
             return getSubstring(getSubmatchRange());
         }
 
@@ -482,6 +496,14 @@
             return getFullText().substring(range.getStart(), range.getEnd());
         }
 
+        private void checkSubmatchSupported() {
+            if (!mBundle.containsKey(SUBMATCH_RANGE_LOWER_FIELD)) {
+                throw new UnsupportedOperationException(
+                        "Submatch is not supported with this backend/Android API level "
+                                + "combination");
+            }
+        }
+
         /** Extracts the matching string from the document. */
         private static String getPropertyValues(GenericDocument document, String propertyName) {
             // In IcingLib snippeting is available for only 3 data types i.e String, double and
@@ -499,7 +521,7 @@
         public static final class Builder {
             private final String mPropertyPath;
             private MatchRange mExactMatchRange = new MatchRange(0, 0);
-            private MatchRange mSubmatchRange = new MatchRange(0, 0);
+            @Nullable private MatchRange mSubmatchRange;
             private MatchRange mSnippetRange = new MatchRange(0, 0);
 
             /**
@@ -548,8 +570,11 @@
                 bundle.putString(SearchResult.MatchInfo.PROPERTY_PATH_FIELD, mPropertyPath);
                 bundle.putInt(MatchInfo.EXACT_MATCH_RANGE_LOWER_FIELD, mExactMatchRange.getStart());
                 bundle.putInt(MatchInfo.EXACT_MATCH_RANGE_UPPER_FIELD, mExactMatchRange.getEnd());
-                bundle.putInt(MatchInfo.SUBMATCH_RANGE_LOWER_FIELD, mSubmatchRange.getStart());
-                bundle.putInt(MatchInfo.SUBMATCH_RANGE_UPPER_FIELD, mSubmatchRange.getEnd());
+                if (mSubmatchRange != null) {
+                    // Only populate the submatch fields if it was actually set.
+                    bundle.putInt(MatchInfo.SUBMATCH_RANGE_LOWER_FIELD, mSubmatchRange.getStart());
+                    bundle.putInt(MatchInfo.SUBMATCH_RANGE_UPPER_FIELD, mSubmatchRange.getEnd());
+                }
                 bundle.putInt(MatchInfo.SNIPPET_RANGE_LOWER_FIELD, mSnippetRange.getStart());
                 bundle.putInt(MatchInfo.SNIPPET_RANGE_UPPER_FIELD, mSnippetRange.getEnd());
                 return new MatchInfo(bundle, /*document=*/ null);
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
index 61dd1c6..8e7258a6 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
@@ -299,6 +299,11 @@
         }
         throw IllegalStateException("Failed to stop $runningProcesses")
     }
+
+    @RequiresApi(21)
+    fun pathExists(absoluteFilePath: String): Boolean {
+        return executeCommand("ls $absoluteFilePath").trim() == absoluteFilePath
+    }
 }
 
 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
index 958c781..26a45c3 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
@@ -95,7 +95,8 @@
         name = "linux.process_stats",
         target_buffer = 1,
         process_stats_config = ProcessStatsConfig(
-            proc_stats_poll_ms = 10000
+            proc_stats_poll_ms = 10000,
+            scan_all_processes_on_start = true
         )
     )
 )
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
index 5d5e266a..2b0cf5d 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
@@ -114,6 +114,58 @@
             throw perfettoStartupException("Perfetto tracing failed to start.", null)
         }
         Log.i(LOG_TAG, "Perfetto tracing started successfully with pid $perfettoPid.")
+
+        checkTracingOn()
+    }
+
+    /**
+     * Poll for tracing_on to be set to 1.
+     *
+     * This is a good indicator that tracing is actually enabled (including the app atrace tag), and
+     * that content will be captured in the trace buffer
+     */
+    private fun checkTracingOn(): Unit = userspaceTrace("poll tracing_on") {
+        val path: String = when {
+            Shell.pathExists(TRACING_ON_PATH) -> {
+                TRACING_ON_PATH
+            }
+            Shell.pathExists(TRACING_ON_FALLBACK_PATH) -> {
+                TRACING_ON_FALLBACK_PATH
+            }
+            else -> {
+                throw perfettoStartupException(
+                    "Unable to find path to tracing_on (e.g. $TRACING_ON_PATH)",
+                    null
+                )
+            }
+        }
+
+        val pollTracingOnMaxCount = 50
+        val pollTracingOnMs = 100L
+
+        repeat(pollTracingOnMaxCount) {
+            when (val output = Shell.executeCommand("cat $path").trim()) {
+                "0" -> {
+                    userspaceTrace("wait for trace to start (tracing_on == 1)") {
+                        SystemClock.sleep(pollTracingOnMs)
+                    }
+                }
+                "1" -> {
+                    // success!
+                    Log.i(LOG_TAG, "$path = 1, polled $it times, capture fully started")
+                    return@checkTracingOn
+                }
+                else -> {
+                    throw perfettoStartupException(
+                        "Saw unexpected tracing_on contents: $output",
+                        null
+                    )
+                }
+            }
+        }
+
+        val duration = pollTracingOnMs * pollTracingOnMaxCount
+        throw perfettoStartupException("Error: did not detect tracing on after $duration ms", null)
     }
 
     /**
@@ -294,6 +346,10 @@
         private val SUPPORTED_64_ABIS = setOf("arm64-v8a", "x86_64")
         private val SUPPORTED_32_ABIS = setOf("armeabi")
 
+        // potential paths that tracing_on may reside in
+        private const val TRACING_ON_PATH = "/sys/kernel/tracing/tracing_on"
+        private const val TRACING_ON_FALLBACK_PATH = "/sys/kernel/debug/tracing/tracing_on"
+
         @TestOnly
         fun isAbiSupported(): Boolean {
             Log.d(LOG_TAG, "Supported ABIs: ${Build.SUPPORTED_ABIS.joinToString()}")
diff --git a/benchmark/benchmark-macro/api/1.1.0-beta01.txt b/benchmark/benchmark-macro/api/1.1.0-beta01.txt
index 8ebc594..b5ba91e 100644
--- a/benchmark/benchmark-macro/api/1.1.0-beta01.txt
+++ b/benchmark/benchmark-macro/api/1.1.0-beta01.txt
@@ -74,3 +74,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/api/current.txt b/benchmark/benchmark-macro/api/current.txt
index 8ebc594..b5ba91e 100644
--- a/benchmark/benchmark-macro/api/current.txt
+++ b/benchmark/benchmark-macro/api/current.txt
@@ -74,3 +74,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/api/public_plus_experimental_1.1.0-beta01.txt b/benchmark/benchmark-macro/api/public_plus_experimental_1.1.0-beta01.txt
index 8ebc594..b5ba91e 100644
--- a/benchmark/benchmark-macro/api/public_plus_experimental_1.1.0-beta01.txt
+++ b/benchmark/benchmark-macro/api/public_plus_experimental_1.1.0-beta01.txt
@@ -74,3 +74,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/api/public_plus_experimental_current.txt b/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
index 8ebc594..b5ba91e 100644
--- a/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
+++ b/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
@@ -74,3 +74,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/api/restricted_1.1.0-beta01.txt b/benchmark/benchmark-macro/api/restricted_1.1.0-beta01.txt
index 4a59db7..f7121a1e 100644
--- a/benchmark/benchmark-macro/api/restricted_1.1.0-beta01.txt
+++ b/benchmark/benchmark-macro/api/restricted_1.1.0-beta01.txt
@@ -83,3 +83,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/api/restricted_current.txt b/benchmark/benchmark-macro/api/restricted_current.txt
index 4a59db7..f7121a1e 100644
--- a/benchmark/benchmark-macro/api/restricted_current.txt
+++ b/benchmark/benchmark-macro/api/restricted_current.txt
@@ -83,3 +83,10 @@
 
 }
 
+package androidx.benchmark.macro.perfetto {
+
+  public final class ForceTracingKt {
+  }
+
+}
+
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt
index 4cf3f2b..e9996c5 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt
@@ -38,6 +38,7 @@
 import org.junit.runner.RunWith
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
 
 /**
  * Tests for PerfettoCapture
@@ -90,39 +91,43 @@
 
         perfettoCapture.start(listOf(Packages.TEST))
 
-        verifyTraceEnable(true)
+        assertTrue(
+            Trace.isEnabled(),
+            "In-process tracing should be enabled immediately after trace capture is started"
+        )
 
-        // TODO: figure out why this sleep (200ms+) is needed - possibly related to b/194105203
-        Thread.sleep(500)
-
-        // Tracing non-trivial duration for manual debugging/verification
-        trace(CUSTOM_TRACE_SECTION_LABEL_1) { Thread.sleep(20) }
-        trace(CUSTOM_TRACE_SECTION_LABEL_2) { Thread.sleep(20) }
+        /**
+         * Trace section labels, in order
+         *
+         * We trace for non-trivial duration both to enable easier manual debugging, but also to
+         * help clarify problems in front/back trace truncation, with indication of severity.
+         *
+         * We use unique, app tag names to avoid conflicting with other legitimate platform tracing.
+         */
+        val traceSectionLabels = List(20) {
+            "PerfettoCaptureTest_$it".also { label ->
+                trace(label) { Thread.sleep(50) }
+            }
+        }
 
         perfettoCapture.stop(traceFilePath)
 
         val matchingSlices = PerfettoTraceProcessor.querySlices(
             absoluteTracePath = traceFilePath,
-            CUSTOM_TRACE_SECTION_LABEL_1,
-            CUSTOM_TRACE_SECTION_LABEL_2
+            "PerfettoCaptureTest_%"
         )
 
         // Note: this test avoids validating platform-triggered trace sections, to avoid flakes
         // from legitimate (and coincidental) platform use during test.
         assertEquals(
-            listOf(CUSTOM_TRACE_SECTION_LABEL_1, CUSTOM_TRACE_SECTION_LABEL_2),
+            traceSectionLabels,
             matchingSlices.sortedBy { it.ts }.map { it.name }
         )
         matchingSlices
             .forEach {
-                assertTrue(it.dur > 15_000_000) // should be at least 15ms
+                assertTrue(it.dur > 30_000_000) // should be at least 30ms
             }
     }
-
-    companion object {
-        const val CUSTOM_TRACE_SECTION_LABEL_1 = "PerfettoCaptureTest_1"
-        const val CUSTOM_TRACE_SECTION_LABEL_2 = "PerfettoCaptureTest_2"
-    }
 }
 
 fun verifyTraceEnable(enabled: Boolean) {
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.kt
index 7572b4a..54dd8a0 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.kt
@@ -43,13 +43,47 @@
             absoluteTracePath = traceFile.absolutePath,
             captureApiLevel = api,
             targetPackageName = "androidx.benchmark.integration.macrobenchmark.target",
-            testPackageName = "androidx.benchmark.integration.macrobenchmark.test"
+            testPackageName = "androidx.benchmark.integration.macrobenchmark.test",
+            startupMode = startupMode
         )
 
         assertEquals(expected = expectedMetrics, actual = startupSubMetrics)
     }
 
     @Test
+    fun fixedApi24Cold() = validateFixedTrace(
+        api = 24,
+        startupMode = StartupMode.COLD,
+        StartupTimingQuery.SubMetrics(
+            timeToInitialDisplayNs = 358237760,
+            timeToFullDisplayNs = 784769167,
+            timelineRange = 269543917431669..269544702200836
+        )
+    )
+
+    @Test
+    fun fixedApi24Warm() = validateFixedTrace(
+        api = 24,
+        startupMode = StartupMode.WARM,
+        StartupTimingQuery.SubMetrics(
+            timeToInitialDisplayNs = 135008333,
+            timeToFullDisplayNs = 598500833,
+            timelineRange = 268757401479247..268757999980080
+        )
+    )
+
+    @Test
+    fun fixedApi24Hot() = validateFixedTrace(
+        api = 24,
+        startupMode = StartupMode.HOT,
+        StartupTimingQuery.SubMetrics(
+            timeToInitialDisplayNs = 54248802,
+            timeToFullDisplayNs = 529336511,
+            timelineRange = 268727533977218..268728063313729
+        )
+    )
+
+    @Test
     fun fixedApi31Cold() = validateFixedTrace(
         api = 31,
         startupMode = StartupMode.COLD,
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index 598d1f1..34b557d 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -165,9 +165,10 @@
                  * Prior to API 24, every package name was joined into a single setprop which can
                  * overflow, and disable *ALL* app level tracing.
                  *
-                 * For safety here, we only trace the macrobench package on newer platforms.
-                 * Eventually, we should find alternative methods - such as force-enabling tracing
-                 * within macrobench process via reflection.
+                 * For safety here, we only trace the macrobench package on newer platforms, and use
+                 * reflection in the macrobench test process to trace important sections
+                 *
+                 * @see androidx.benchmark.macro.perfetto.ForceTracing
                  */
                 packages = if (Build.VERSION.SDK_INT >= 24) {
                     listOf(packageName, macrobenchPackageName)
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
index 7a639fc..e15d0cd 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
@@ -22,9 +22,9 @@
 import androidx.annotation.RequiresApi
 import androidx.benchmark.DeviceInfo
 import androidx.benchmark.Shell
+import androidx.benchmark.macro.perfetto.forceTrace
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
-import androidx.tracing.trace
 
 /**
  * Provides access to common operations in app automation, such as killing the app,
@@ -73,7 +73,7 @@
      *
      * @param intent Specifies which app/Activity should be launched.
      */
-    public fun startActivityAndWait(intent: Intent): Unit = trace("startActivityAndWait") {
+    public fun startActivityAndWait(intent: Intent): Unit = forceTrace("startActivityAndWait") {
         // Must launch with new task, as we're not launching from an existing task
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
         if (launchWithClearTask) {
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/ForceTracing.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/ForceTracing.kt
new file mode 100644
index 0000000..2f0c372
--- /dev/null
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/ForceTracing.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.benchmark.macro.perfetto
+
+import android.os.Build
+import android.os.Trace
+
+/**
+ * Wrapper that uses standard app tracing above API 24, and forces trace sections via reflection
+ * on 23 and below.
+ *
+ * Prior to API 24, all apps with tracing enabled had to be listed in a single setprop, fully
+ * enumerated by package name. As a setprop can only be 91 chars long, it's easy to hit cases where
+ * a macrobenchmark and its target can't fit into this single setprop. For this reason, prior to 24,
+ * the macrobenchmark process doesn't rely on the app tracing tag, and instead forces trace sections
+ * via reflection, and the TRACE_TAG_ALWAYS tag.
+ */
+internal object ForceTracing {
+    private val traceBeginMethod = if (Build.VERSION.SDK_INT < 24) {
+        android.os.Trace::class.java.getMethod(
+            "traceBegin",
+            Long::class.javaPrimitiveType,
+            String::class.javaPrimitiveType
+        )
+    } else null
+
+    private val traceEndMethod = if (Build.VERSION.SDK_INT < 24) {
+        android.os.Trace::class.java.getMethod(
+            "traceEnd",
+            Long::class.javaPrimitiveType,
+            String::class.javaPrimitiveType
+        )
+    } else null
+
+    private const val TRACE_TAG_ALWAYS = 1L shl 0 // copied from android.os.Trace
+
+    fun begin(label: String) {
+        if (Build.VERSION.SDK_INT < 24) {
+            traceBeginMethod!!.invoke(null, TRACE_TAG_ALWAYS, label)
+        } else {
+            Trace.beginSection(label)
+        }
+    }
+
+    fun end() {
+        if (Build.VERSION.SDK_INT < 24) {
+            traceEndMethod!!.invoke(null, TRACE_TAG_ALWAYS)
+        } else {
+            Trace.endSection()
+        }
+    }
+}
+
+internal inline fun <T> forceTrace(label: String, block: () -> T): T {
+    ForceTracing.begin(label)
+    return try {
+        block()
+    } finally {
+        ForceTracing.end()
+    }
+}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt
index 68803a4c..8bed227 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt
@@ -72,6 +72,8 @@
 
     /**
      * Query a trace for a list of slices - name, timestamp, and duration.
+     *
+     * Note that sliceNames may include wildcard matches, such as `foo%`
      */
     fun querySlices(
         absoluteTracePath: String,
@@ -79,7 +81,7 @@
     ): List<Slice> {
         val whereClause = sliceNames
             .joinToString(separator = " OR ") {
-                "slice.name = '$it'"
+                "slice.name LIKE \"$it\""
             }
 
         return Slice.parseListFromQueryResult(
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt
index d14353c..91d194b 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt
@@ -16,7 +16,18 @@
 
 package androidx.benchmark.macro.perfetto
 
+import androidx.benchmark.macro.StartupMode
+
 internal object StartupTimingQuery {
+
+    /**
+     * On older platforms, process name may be truncated, especially in cold startup traces,
+     * when the process name dump at trace begin happens _before_ app process is created.
+     *
+     * @see perfetto.protos.ProcessStatsConfig.scan_all_processes_on_start
+     */
+    private fun String.truncatedProcessName() = takeLast(15)
+
     private fun getFullQuery(testProcessName: String, targetProcessName: String) = """
         ------ Select all startup-relevant slices from slice table
         SELECT
@@ -28,12 +39,20 @@
             INNER JOIN thread USING(utid)
             INNER JOIN process USING(upid)
         WHERE (
+            -- Test process starts before tracing, so it shouldn't have truncation problem
             (process.name LIKE "$testProcessName" AND slice.name LIKE "startActivityAndWait") OR
-            (process.name LIKE "$targetProcessName" AND (
-                (slice.name LIKE "Choreographer#doFrame%" AND process.pid LIKE thread.tid) OR
-                (slice.name LIKE "reportFullyDrawn() for %" AND process.pid LIKE thread.tid) OR
-                (slice.name LIKE "DrawFrame%" AND thread.name LIKE "RenderThread")
-            ))
+            (
+                (
+                    -- check for full or truncated process name (can happen on older platforms)
+                    process.name LIKE "$targetProcessName" OR
+                    process.name LIKE "${targetProcessName.truncatedProcessName()}"
+                ) AND (
+                    (slice.name LIKE "activityResume" AND process.pid LIKE thread.tid) OR
+                    (slice.name LIKE "Choreographer#doFrame%" AND process.pid LIKE thread.tid) OR
+                    (slice.name LIKE "reportFullyDrawn() for %" AND process.pid LIKE thread.tid) OR
+                    (slice.name LIKE "DrawFrame%" AND thread.name LIKE "RenderThread")
+                )
+            )
         )
         ------ Add in async slices
         UNION
@@ -56,8 +75,9 @@
         StartActivityAndWait,
         Launching,
         ReportFullyDrawn,
-        UiThread,
-        RenderThread
+        FrameUiThread,
+        FrameRenderThread,
+        ActivityResume
     }
 
     data class SubMetrics(
@@ -76,11 +96,27 @@
         )
     }
 
+    private fun findEndRenderTimeForUiFrame(
+        uiSlices: List<Slice>,
+        rtSlices: List<Slice>,
+        predicate: (Slice) -> Boolean
+    ): Long {
+        // find first UI slice that corresponds with the predicate
+        val uiSlice = uiSlices.first(predicate)
+
+        // find corresponding rt slice
+        val rtSlice = rtSlices.first { rtSlice ->
+            rtSlice.ts > uiSlice.ts
+        }
+        return rtSlice.endTs
+    }
+
     fun getFrameSubMetrics(
         absoluteTracePath: String,
         captureApiLevel: Int,
         targetPackageName: String,
-        testPackageName: String
+        testPackageName: String,
+        startupMode: StartupMode
     ): SubMetrics? {
         val queryResult = PerfettoTraceProcessor.rawQuery(
             absoluteTracePath = absoluteTracePath,
@@ -97,10 +133,11 @@
                 when {
                     // note: we use "startsWith" as many of these have more details
                     // appended to the slice name in more recent platform versions
-                    it.name.startsWith("Choreographer#doFrame") -> StartupSliceType.UiThread
-                    it.name.startsWith("DrawFrame") -> StartupSliceType.RenderThread
+                    it.name.startsWith("Choreographer#doFrame") -> StartupSliceType.FrameUiThread
+                    it.name.startsWith("DrawFrame") -> StartupSliceType.FrameRenderThread
                     it.name.startsWith("launching") -> StartupSliceType.Launching
                     it.name.startsWith("reportFullyDrawn") -> StartupSliceType.ReportFullyDrawn
+                    it.name == "activityResume" -> StartupSliceType.ActivityResume
                     it.name == "startActivityAndWait" -> StartupSliceType.StartActivityAndWait
                     else -> throw IllegalStateException("Unexpected slice $it")
                 }
@@ -108,35 +145,44 @@
 
         val startActivityAndWaitSlice = groupedData[StartupSliceType.StartActivityAndWait]?.first()
             ?: return null
-        val launchingSlice = groupedData[StartupSliceType.Launching]?.firstOrNull {
-            // find first "launching" slice that starts within startActivityAndWait
-            // verify full name only on API 23+, since before package name not specified
-            startActivityAndWaitSlice.contains(it.ts) &&
-                (captureApiLevel < 23 || it.name == "launching: $targetPackageName")
-        } ?: return null
+
+        val uiSlices = groupedData.getOrElse(StartupSliceType.FrameUiThread) { listOf() }
+        val rtSlices = groupedData.getOrElse(StartupSliceType.FrameRenderThread) { listOf() }
+
+        val startTs: Long
+        val initialDisplayTs: Long
+        if (captureApiLevel >= 29 || startupMode != StartupMode.HOT) {
+            val launchingSlice = groupedData[StartupSliceType.Launching]?.firstOrNull {
+                // find first "launching" slice that starts within startActivityAndWait
+                // verify full name only on API 23+, since before package name not specified
+                startActivityAndWaitSlice.contains(it.ts) &&
+                    (captureApiLevel < 23 || it.name == "launching: $targetPackageName")
+            } ?: return null
+            startTs = launchingSlice.ts
+            initialDisplayTs = launchingSlice.endTs
+        } else {
+            // Prior to API 29, hot starts weren't traced with the launching slice, so we do a best
+            // guess - the time taken to Activity#onResume, and then produce the next frame.
+            startTs = groupedData[StartupSliceType.ActivityResume]?.first()?.ts
+                ?: return null
+            initialDisplayTs = findEndRenderTimeForUiFrame(uiSlices, rtSlices) { uiSlice ->
+                uiSlice.ts > startTs
+            }
+        }
 
         val reportFullyDrawnSlice = groupedData[StartupSliceType.ReportFullyDrawn]?.firstOrNull()
 
         val reportFullyDrawnEndTs: Long? = reportFullyDrawnSlice?.let {
-            val uiSlices = groupedData.getOrElse(StartupSliceType.UiThread) { listOf() }
-            val rtSlices = groupedData.getOrElse(StartupSliceType.RenderThread) { listOf() }
-
             // find first uiSlice with end after reportFullyDrawn (reportFullyDrawn may happen
             // during or before a given frame)
-            val uiSlice = uiSlices.first { uiSlice ->
+            findEndRenderTimeForUiFrame(uiSlices, rtSlices) { uiSlice ->
                 uiSlice.endTs > reportFullyDrawnSlice.ts
             }
-
-            // And find corresponding rt slice
-            val rtSlice = rtSlices.first { rtSlice ->
-                rtSlice.ts > uiSlice.ts
-            }
-            rtSlice.endTs
         }
 
         return SubMetrics(
-            startTs = launchingSlice.ts,
-            initialDisplayTs = launchingSlice.endTs,
+            startTs = startTs,
+            initialDisplayTs = initialDisplayTs,
             fullDisplayTs = reportFullyDrawnEndTs,
         )
     }
diff --git a/biometric/biometric/src/androidTest/java/androidx/biometric/DeviceUtilsTest.java b/biometric/biometric/src/androidTest/java/androidx/biometric/DeviceUtilsTest.java
index 791d38f..396f47e 100644
--- a/biometric/biometric/src/androidTest/java/androidx/biometric/DeviceUtilsTest.java
+++ b/biometric/biometric/src/androidTest/java/androidx/biometric/DeviceUtilsTest.java
@@ -131,4 +131,28 @@
         assertThat(DeviceUtils.canAssumeStrongBiometrics(mContext, "Myphone")).isFalse();
         assertThat(DeviceUtils.canAssumeStrongBiometrics(mContext, "My phone2")).isFalse();
     }
+
+    @Test
+    public void testShouldUseKeyguardManagerForBiometricAndCredential() {
+        final String[] vendors = {"buy-n-large", "pizza planet"};
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getStringArray(R.array.keyguard_biometric_and_credential_vendors))
+                .thenReturn(vendors);
+
+        final boolean isApi29 = Build.VERSION.SDK_INT == Build.VERSION_CODES.Q;
+        assertThat(DeviceUtils.shouldUseKeyguardManagerForBiometricAndCredential(
+                mContext, "buy-n-large")).isEqualTo(isApi29);
+        assertThat(DeviceUtils.shouldUseKeyguardManagerForBiometricAndCredential(
+                mContext, "BUY-N-LARGE")).isEqualTo(isApi29);
+        assertThat(DeviceUtils.shouldUseKeyguardManagerForBiometricAndCredential(
+                mContext, "pizza planet")).isEqualTo(isApi29);
+        assertThat(DeviceUtils.shouldUseKeyguardManagerForBiometricAndCredential(
+                mContext, "Pizza Planet")).isEqualTo(isApi29);
+        assertThat(DeviceUtils.shouldUseKeyguardManagerForBiometricAndCredential(
+                mContext, "dinoco")).isFalse();
+        assertThat(DeviceUtils.shouldUseKeyguardManagerForBiometricAndCredential(
+                mContext, "buy n large")).isFalse();
+        assertThat(DeviceUtils.shouldUseKeyguardManagerForBiometricAndCredential(
+                mContext, "pizza planet plus")).isFalse();
+    }
 }
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
index ea9fc39..3895905 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricFragment.java
@@ -975,11 +975,21 @@
      */
     private void handleConfirmCredentialResult(int resultCode) {
         if (resultCode == Activity.RESULT_OK) {
+            final BiometricViewModel viewModel = getViewModel();
+            @BiometricPrompt.AuthenticationResultType final int authenticationType;
+            if (viewModel != null && viewModel.isUsingKeyguardManagerForBiometricAndCredential()) {
+                // If using KeyguardManager for biometric and credential auth, we don't know which
+                // actual authentication type was used.
+                authenticationType = BiometricPrompt.AUTHENTICATION_RESULT_TYPE_UNKNOWN;
+                viewModel.setUsingKeyguardManagerForBiometricAndCredential(false);
+            } else {
+                authenticationType = BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL;
+            }
+
             // Device credential auth succeeded. This is incompatible with crypto for API <30.
             sendSuccessAndDismiss(
                     new BiometricPrompt.AuthenticationResult(
-                            null /* crypto */,
-                            BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL));
+                            null /* crypto */, authenticationType));
         } else {
             // Device credential auth failed. Assume this is due to the user canceling.
             sendErrorAndDismiss(
@@ -1204,9 +1214,28 @@
      * @return Whether this fragment should use {@link KeyguardManager} directly.
      */
     private boolean isKeyguardManagerNeededForAuthentication() {
+        final Context context = getContext();
+
+        // Devices from some vendors should use KeyguardManager for authentication if both biometric
+        // and credential authenticator types are allowed (on API 29).
+        if (context != null && DeviceUtils.shouldUseKeyguardManagerForBiometricAndCredential(
+                context, Build.MANUFACTURER)) {
+
+            final BiometricViewModel viewModel = getViewModel();
+            @BiometricManager.AuthenticatorTypes int allowedAuthenticators = viewModel != null
+                    ? viewModel.getAllowedAuthenticators()
+                    : 0;
+
+            if (viewModel != null
+                    && AuthenticatorUtils.isWeakBiometricAllowed(allowedAuthenticators)
+                    && AuthenticatorUtils.isDeviceCredentialAllowed(allowedAuthenticators)) {
+                viewModel.setUsingKeyguardManagerForBiometricAndCredential(true);
+                return true;
+            }
+        }
+
         // On API 29, BiometricPrompt fails to launch the confirm device credential Settings
         // activity if no biometric hardware is present.
-        final Context context = getContext();
         return Build.VERSION.SDK_INT == Build.VERSION_CODES.Q
                 && !mInjector.isFingerprintHardwarePresent(context)
                 && !mInjector.isFaceHardwarePresent(context)
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
index 9f69bb8..0b71759 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricViewModel.java
@@ -221,6 +221,12 @@
     private boolean mIsIgnoringCancel;
 
     /**
+     * Whether {@link android.app.KeyguardManager} is being used directly for authentication with
+     * both biometric and credential authenticator types allowed.
+     */
+    private boolean mIsUsingKeyguardManagerForBiometricAndCredential;
+
+    /**
      * Information associated with a successful authentication attempt.
      */
     @Nullable private MutableLiveData<BiometricPrompt.AuthenticationResult> mAuthenticationResult;
@@ -501,6 +507,16 @@
         mIsIgnoringCancel = ignoringCancel;
     }
 
+    boolean isUsingKeyguardManagerForBiometricAndCredential() {
+        return mIsUsingKeyguardManagerForBiometricAndCredential;
+    }
+
+    void setUsingKeyguardManagerForBiometricAndCredential(
+            boolean usingKeyguardManagerForBiometricAndCredential) {
+        mIsUsingKeyguardManagerForBiometricAndCredential =
+                usingKeyguardManagerForBiometricAndCredential;
+    }
+
     @NonNull
     LiveData<BiometricPrompt.AuthenticationResult> getAuthenticationResult() {
         if (mAuthenticationResult == null) {
diff --git a/biometric/biometric/src/main/java/androidx/biometric/DeviceUtils.java b/biometric/biometric/src/main/java/androidx/biometric/DeviceUtils.java
index 077c509..899f7d1 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/DeviceUtils.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/DeviceUtils.java
@@ -20,6 +20,7 @@
 import android.os.Build;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 /**
  * Utility class for specifying custom behavior based on the vendor and model of the device.
@@ -100,6 +101,27 @@
     }
 
     /**
+     * Checks if the current device should directly invoke
+     * {@link android.app.KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence,
+     * CharSequence)} for authentication when both <strong>Class 2</strong> (formerly
+     * <strong>Weak</strong>) biometrics and device credentials (i.e. PIN, pattern, or password) are
+     * allowed.
+     *
+     * @param context The application or activity context.
+     * @param vendor Name of the device vendor/manufacturer.
+     * @return Whether the device should use {@link android.app.KeyguardManager} for authentication
+     * if both <strong>Class 2</strong> biometrics and device credentials are allowed.
+     */
+    static boolean shouldUseKeyguardManagerForBiometricAndCredential(
+            @NonNull Context context, @Nullable String vendor) {
+        if (Build.VERSION.SDK_INT != Build.VERSION_CODES.Q) {
+            // This workaround is only needed for API 29.
+            return false;
+        }
+        return isVendorInList(context, vendor, R.array.keyguard_biometric_and_credential_vendors);
+    }
+
+    /**
      * Checks if the name of the current device vendor matches one in the given string array
      * resource.
      *
diff --git a/biometric/biometric/src/main/res/values/devices.xml b/biometric/biometric/src/main/res/values/devices.xml
index 2089d3f..b152e78 100644
--- a/biometric/biometric/src/main/res/values/devices.xml
+++ b/biometric/biometric/src/main/res/values/devices.xml
@@ -82,4 +82,14 @@
         <item>Pixel 4</item>
         <item>Pixel 4 XL</item>
     </string-array>
+
+    <!--
+    List of known device vendors for which authentication with both Class 2 (Weak) biometrics
+    and device credential (PIN/pattern/password) as allowed authenticator types should use
+    KeyguardManager#createConfirmDeviceCredentialIntent(CharSequence, CharSequence) on API 29.
+    -->
+    <string-array name="keyguard_biometric_and_credential_vendors">
+        <item>google</item>
+        <item>samsung</item>
+    </string-array>
 </resources>
diff --git a/biometric/integration-tests/testapp/src/androidTest/java/androidx/biometric/integration/testapp/BiometricPromptEnrolledTest.kt b/biometric/integration-tests/testapp/src/androidTest/java/androidx/biometric/integration/testapp/BiometricPromptEnrolledTest.kt
index c368ab3..86a3a81 100644
--- a/biometric/integration-tests/testapp/src/androidTest/java/androidx/biometric/integration/testapp/BiometricPromptEnrolledTest.kt
+++ b/biometric/integration-tests/testapp/src/androidTest/java/androidx/biometric/integration/testapp/BiometricPromptEnrolledTest.kt
@@ -126,6 +126,9 @@
 
     @Test
     fun testBiometricOrCredentialAuth_SendsError_WhenCanceledOnConfigurationChange() {
+        // Prompt isn't canceled on configuration change for some devices on API 29 (b/202975762).
+        assumeFalse(Build.VERSION.SDK_INT == Build.VERSION_CODES.Q)
+
         onView(withId(R.id.allow_device_credential_checkbox)).perform(click())
         testBiometricOnlyAuth_SendsError_WhenCanceledOnConfigurationChange()
     }
@@ -142,7 +145,7 @@
 
     @Test
     fun testBiometricOrCredentialAuth_SendsError_WhenActivityBackgrounded() {
-        // TODO(b/162022588): Fix this for Pixel devices on API 29.
+        // Prompt is not dismissed when backgrounded for Pixel devices on API 29 (b/162022588).
         assumeFalse(Build.VERSION.SDK_INT == Build.VERSION_CODES.Q)
 
         onView(withId(R.id.allow_device_credential_checkbox)).perform(click())
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 38d3c51..ff33954 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -144,7 +144,8 @@
     val isTestingLintItself = (project.path == ":lint-checks:integration-tests")
 
     // If -PupdateLintBaseline was set we should update the baseline if it exists
-    val updateLintBaseline = hasProperty(UPDATE_LINT_BASELINE) && !isTestingLintItself
+    val updateLintBaseline = project.providers.gradleProperty(UPDATE_LINT_BASELINE)
+        .forUseAtConfigurationTime().isPresent() && !isTestingLintItself
 
     lintOptions.apply {
         // Skip lintVital tasks on assemble. We explicitly run lintRelease for libraries.
@@ -202,6 +203,9 @@
             // Broken in 7.0.0-alpha15 due to b/187508590
             disable("InvalidPackage")
 
+            // Reenable after upgradingto 7.1.0-beta01
+            disable("SupportAnnotationUsage")
+
             // Provide stricter enforcement for project types intended to run on a device.
             if (extension.type.compilationTarget == CompilationTarget.DEVICE) {
                 fatal("Assert")
@@ -212,7 +216,6 @@
                 fatal("KotlinPropertyAccess")
                 fatal("LambdaLast")
                 fatal("UnknownNullness")
-                fatal("SupportAnnotationUsage")
 
                 // Only override if not set explicitly.
                 // Some Kotlin projects may wish to disable this.
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt b/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
index b04ea44..4503a9f 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
@@ -185,14 +185,9 @@
     "lintVitalDebug",
     "lintWithExpandProjectionDebug",
     "lintWithoutExpandProjectionDebug",
+    "lintWithNullAwareTypeConverterDebug",
     "lintWithKaptDebug",
-    "lintWithKspDebug",
-
-    // https://github.com/johnrengelman/shadow/issues/707
-    "embeddedPlugin",
-    "repackageBundledJars",
-    "repackageCompiler",
-    "shadowJar"
+    "lintWithKspDebug"
 )
 
 abstract class TaskUpToDateValidator :
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
index 06ff33b..b2152e2 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -20,7 +20,7 @@
  * The list of versions codes of all the libraries in this project.
  */
 object LibraryVersions {
-    val ACTIVITY = Version("1.4.0-rc01")
+    val ACTIVITY = Version("1.5.0-alpha01")
     val ADS_IDENTIFIER = Version("1.0.0-alpha05")
     val ANNOTATION = Version("1.3.0-rc01")
     val ANNOTATION_EXPERIMENTAL = Version("1.2.0-alpha01")
@@ -31,19 +31,19 @@
     val AUTOFILL = Version("1.2.0-beta02")
     val BENCHMARK = Version("1.1.0-beta01")
     val BIOMETRIC = Version("1.2.0-alpha04")
-    val BROWSER = Version("1.4.0-rc01")
+    val BROWSER = Version("1.5.0-alpha01")
     val BUILDSRC_TESTS = Version("1.0.0-alpha01")
-    val CAMERA = Version("1.1.0-alpha10")
-    val CAMERA_EXTENSIONS = Version("1.0.0-alpha30")
+    val CAMERA = Version("1.1.0-alpha11")
+    val CAMERA_EXTENSIONS = Version("1.0.0-alpha31")
     val CAMERA_PIPE = Version("1.0.0-alpha01")
-    val CAMERA_VIEW = Version("1.0.0-alpha30")
+    val CAMERA_VIEW = Version("1.0.0-alpha31")
     val CARDVIEW = Version("1.1.0-alpha01")
     val CAR_APP = Version("1.1.0-beta02")
-    val COLLECTION = Version("1.2.0-beta01")
+    val COLLECTION = Version("1.2.0-beta02")
     val COLLECTION2 = Version("1.2.0-beta01")
     val CONTENTPAGER = Version("1.1.0-alpha01")
     val COMPOSE_MATERIAL3 = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.0.0-alpha01")
-    val COMPOSE = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.1.0-alpha06")
+    val COMPOSE = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.1.0-alpha07")
     val COORDINATORLAYOUT = Version("1.2.0-alpha01")
     val CORE = Version("1.7.0-rc01")
     val CORE_ANIMATION = Version("1.0.0-alpha03")
@@ -91,19 +91,19 @@
     val MEDIAROUTER = Version("1.3.0-alpha01")
     val METRICS = Version("1.0.0-alpha01")
     val NAVIGATION = Version("2.4.0-beta01")
-    val PAGING = Version("3.1.0-beta01")
-    val PAGING_COMPOSE = Version("1.0.0-alpha14")
+    val PAGING = Version("3.1.0-beta02")
+    val PAGING_COMPOSE = Version("1.0.0-alpha15")
     val PALETTE = Version("1.1.0-alpha01")
     val PRINT = Version("1.1.0-beta01")
     val PERCENTLAYOUT = Version("1.1.0-alpha01")
     val PREFERENCE = Version("1.2.0-alpha01")
-    val PROFILEINSTALLER = Version("1.1.0-alpha07")
+    val PROFILEINSTALLER = Version("1.1.0-alpha08")
     val RECOMMENDATION = Version("1.1.0-alpha01")
     val RECYCLERVIEW = Version("1.3.0-alpha02")
     val RECYCLERVIEW_SELECTION = Version("1.2.0-alpha02")
     val REMOTECALLBACK = Version("1.0.0-alpha02")
     val RESOURCEINSPECTION = Version("1.0.0-rc01")
-    val ROOM = Version("2.4.0-beta01")
+    val ROOM = Version("2.4.0-beta02")
     val SAVEDSTATE = Version("1.2.0-alpha01")
     val SECURITY = Version("1.1.0-alpha04")
     val SECURITY_APP_AUTHENTICATOR = Version("1.0.0-alpha03")
@@ -117,7 +117,7 @@
     val SLICE_REMOTECALLBACK = Version("1.0.0-alpha01")
     val SLIDINGPANELAYOUT = Version("1.2.0-beta02")
     val STARTUP = Version("1.2.0-alpha01")
-    val SQLITE = Version("2.2.0-beta01")
+    val SQLITE = Version("2.2.0-beta02")
     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/shadow/AndroidXDontIncludeResourceTransformer.kt b/buildSrc/public/src/main/kotlin/androidx/build/shadow/AndroidXDontIncludeResourceTransformer.kt
deleted file mode 100644
index 23d636e..0000000
--- a/buildSrc/public/src/main/kotlin/androidx/build/shadow/AndroidXDontIncludeResourceTransformer.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.build.shadow
-
-import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
-import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
-import org.gradle.api.file.FileTreeElement
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.Optional
-import shadow.org.apache.tools.zip.ZipOutputStream
-
-/**
- * Copy of upstream DontIncludeResourceTransformer that has a fix for Gradle 7.0
- * https://github.com/johnrengelman/shadow/blob/master/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/DontIncludeResourceTransformer.groovy
- */
-class AndroidXDontIncludeResourceTransformer : Transformer {
-    @get:[Input Optional]
-    var resource: String? = null
-    override fun canTransformResource(element: FileTreeElement?): Boolean {
-        val path = element?.relativePath?.pathString ?: return false
-        val resourceSuffix = resource ?: return false
-        if (resourceSuffix.isNotEmpty() && path.endsWith(resourceSuffix)) {
-            return true
-        }
-        return false
-    }
-
-    override fun transform(context: TransformerContext?) {
-        // No-op
-    }
-
-    override fun hasTransformedResource(): Boolean = false
-
-    override fun modifyOutputStream(p0: ZipOutputStream?, p1: Boolean) {
-        // No-op
-    }
-}
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index 37d78e5..d57d75f 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -42,7 +42,7 @@
   }
 
   @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
-    method public android.net.Uri getCollection();
+    method public android.net.Uri getCollectionUri();
     method public android.content.ContentResolver getContentResolver();
     method public android.content.ContentValues getContentValues();
     method public long getFileSizeLimit();
diff --git a/camera/camera-video/api/public_plus_experimental_current.txt b/camera/camera-video/api/public_plus_experimental_current.txt
index 37d78e5..d57d75f 100644
--- a/camera/camera-video/api/public_plus_experimental_current.txt
+++ b/camera/camera-video/api/public_plus_experimental_current.txt
@@ -42,7 +42,7 @@
   }
 
   @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
-    method public android.net.Uri getCollection();
+    method public android.net.Uri getCollectionUri();
     method public android.content.ContentResolver getContentResolver();
     method public android.content.ContentValues getContentValues();
     method public long getFileSizeLimit();
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index c890208..3bce005 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -42,7 +42,7 @@
   }
 
   @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
-    method public android.net.Uri getCollection();
+    method public android.net.Uri getCollectionUri();
     method public android.content.ContentResolver getContentResolver();
     method public android.content.ContentValues getContentValues();
     method public long getFileSizeLimit();
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
index c59f157..c221b5b 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/OutputOptionsTest.kt
@@ -71,7 +71,7 @@
         ).setContentValues(contentValues).setFileSizeLimit(FILE_SIZE_LIMIT).build()
 
         assertThat(mediaStoreOutputOptions.contentResolver).isEqualTo(contentResolver)
-        assertThat(mediaStoreOutputOptions.collection).isEqualTo(
+        assertThat(mediaStoreOutputOptions.collectionUri).isEqualTo(
             MediaStore.Video.Media.EXTERNAL_CONTENT_URI
         )
         assertThat(mediaStoreOutputOptions.contentValues).isEqualTo(contentValues)
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java b/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java
index 6f13547..a1e2d49 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/MediaStoreOutputOptions.java
@@ -40,9 +40,9 @@
  * contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "NEW_VIDEO");
  * contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
  *
- * MediaStoreOutputOptions options = MediaStoreOutputOptions.builder()
- *         .setContentResolver(contentResolver)
- *         .setCollection(MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
+ * MediaStoreOutputOptions options =
+ *         new MediaStoreOutputOptions.Builder(
+ *             contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
  *         .setContentValues(contentValues)
  *         .build();
  *
@@ -91,8 +91,8 @@
      * @see Builder#Builder(ContentResolver, Uri)
      */
     @NonNull
-    public Uri getCollection() {
-        return mMediaStoreOutputOptionsInternal.getCollection();
+    public Uri getCollectionUri() {
+        return mMediaStoreOutputOptionsInternal.getCollectionUri();
     }
 
     /**
@@ -174,7 +174,7 @@
         public Builder(@NonNull ContentResolver contentResolver, @NonNull Uri collectionUri) {
             Preconditions.checkNotNull(contentResolver, "Content resolver can't be null.");
             Preconditions.checkNotNull(collectionUri, "Collection Uri can't be null.");
-            mInternalBuilder.setContentResolver(contentResolver).setCollection(collectionUri);
+            mInternalBuilder.setContentResolver(contentResolver).setCollectionUri(collectionUri);
         }
 
         /**
@@ -229,7 +229,7 @@
         @NonNull
         abstract ContentResolver getContentResolver();
         @NonNull
-        abstract Uri getCollection();
+        abstract Uri getCollectionUri();
         @NonNull
         abstract ContentValues getContentValues();
         abstract long getFileSizeLimit();
@@ -239,7 +239,7 @@
             @NonNull
             abstract Builder setContentResolver(@NonNull ContentResolver contentResolver);
             @NonNull
-            abstract Builder setCollection(@NonNull Uri collectionUri);
+            abstract Builder setCollectionUri(@NonNull Uri collectionUri);
             @NonNull
             abstract Builder setContentValues(@NonNull ContentValues contentValues);
             @NonNull
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 bbc4191..a7390f2 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
@@ -1361,7 +1361,7 @@
                 contentValues.put(MediaStore.Video.Media.IS_PENDING, PENDING);
             }
             Uri outputUri = mediaStoreOutputOptions.getContentResolver().insert(
-                    mediaStoreOutputOptions.getCollection(), contentValues);
+                    mediaStoreOutputOptions.getCollectionUri(), contentValues);
             if (outputUri == null) {
                 throw new IOException("Unable to create MediaStore entry.");
             }
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/templates/SignInTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/templates/SignInTemplateDemoScreen.java
index 5d005f4..30cbbbd 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/templates/SignInTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/templates/SignInTemplateDemoScreen.java
@@ -183,7 +183,8 @@
 
         return new SignInTemplate.Builder(signInMethod)
                 .addAction(mProviderSignInAction)
-                .addAction(mQRCodeSignInAction)
+                .addAction(getCarContext().getCarAppApiLevel() > CarAppApiLevels.LEVEL_3
+                        ? mQRCodeSignInAction : mPinSignInAction)
                 .setTitle("Sign in")
                 .setInstructions("Enter your credentials")
                 .setHeaderAction(Action.BACK)
@@ -251,7 +252,8 @@
 
         return new SignInTemplate.Builder(signInMethod)
                 .addAction(mProviderSignInAction)
-                .addAction(mQRCodeSignInAction)
+                .addAction(getCarContext().getCarAppApiLevel() > CarAppApiLevels.LEVEL_3
+                        ? mQRCodeSignInAction : mPinSignInAction)
                 .setTitle("Sign in")
                 .setInstructions("Username: " + mUsername)
                 .setHeaderAction(Action.BACK)
diff --git a/collection/collection-ktx/api/1.2.0-beta02.txt b/collection/collection-ktx/api/1.2.0-beta02.txt
new file mode 100644
index 0000000..bca313f
--- /dev/null
+++ b/collection/collection-ktx/api/1.2.0-beta02.txt
@@ -0,0 +1,45 @@
+// Signature format: 4.0
+package androidx.collection {
+
+  public final class ArrayMapKt {
+    method public static inline <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf();
+    method public static <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf(kotlin.Pair<? extends K,? extends V>... pairs);
+  }
+
+  public final class ArraySetKt {
+    method public static inline <T> androidx.collection.ArraySet<T> arraySetOf();
+    method public static <T> androidx.collection.ArraySet<T> arraySetOf(T?... values);
+  }
+
+  public final class LongSparseArrayKt {
+    method public static inline operator <T> boolean contains(androidx.collection.LongSparseArray<T>, long key);
+    method public static inline <T> void forEach(androidx.collection.LongSparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Long,? super T,kotlin.Unit> action);
+    method public static inline <T> T! getOrDefault(androidx.collection.LongSparseArray<T>, long key, T? defaultValue);
+    method public static inline <T> T! getOrElse(androidx.collection.LongSparseArray<T>, long key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+    method public static inline <T> int getSize(androidx.collection.LongSparseArray<T>);
+    method public static inline <T> boolean isNotEmpty(androidx.collection.LongSparseArray<T>);
+    method public static <T> kotlin.collections.LongIterator keyIterator(androidx.collection.LongSparseArray<T>);
+    method public static operator <T> androidx.collection.LongSparseArray<T> plus(androidx.collection.LongSparseArray<T>, androidx.collection.LongSparseArray<T> other);
+    method public static inline operator <T> void set(androidx.collection.LongSparseArray<T>, long key, T? value);
+    method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.LongSparseArray<T>);
+  }
+
+  public final class LruCacheKt {
+    method public static inline <K, V> androidx.collection.LruCache<K,V> lruCache(int maxSize, optional kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf, optional kotlin.jvm.functions.Function1<? super K,? extends V> create, optional kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved);
+  }
+
+  public final class SparseArrayKt {
+    method public static inline operator <T> boolean contains(androidx.collection.SparseArrayCompat<T>, int key);
+    method public static inline <T> void forEach(androidx.collection.SparseArrayCompat<T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+    method public static inline <T> T! getOrDefault(androidx.collection.SparseArrayCompat<T>, int key, T? defaultValue);
+    method public static inline <T> T! getOrElse(androidx.collection.SparseArrayCompat<T>, int key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+    method public static inline <T> int getSize(androidx.collection.SparseArrayCompat<T>);
+    method public static inline <T> boolean isNotEmpty(androidx.collection.SparseArrayCompat<T>);
+    method public static <T> kotlin.collections.IntIterator keyIterator(androidx.collection.SparseArrayCompat<T>);
+    method public static operator <T> androidx.collection.SparseArrayCompat<T> plus(androidx.collection.SparseArrayCompat<T>, androidx.collection.SparseArrayCompat<T> other);
+    method public static inline operator <T> void set(androidx.collection.SparseArrayCompat<T>, int key, T? value);
+    method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.SparseArrayCompat<T>);
+  }
+
+}
+
diff --git a/collection/collection-ktx/api/public_plus_experimental_1.2.0-beta02.txt b/collection/collection-ktx/api/public_plus_experimental_1.2.0-beta02.txt
new file mode 100644
index 0000000..bca313f
--- /dev/null
+++ b/collection/collection-ktx/api/public_plus_experimental_1.2.0-beta02.txt
@@ -0,0 +1,45 @@
+// Signature format: 4.0
+package androidx.collection {
+
+  public final class ArrayMapKt {
+    method public static inline <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf();
+    method public static <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf(kotlin.Pair<? extends K,? extends V>... pairs);
+  }
+
+  public final class ArraySetKt {
+    method public static inline <T> androidx.collection.ArraySet<T> arraySetOf();
+    method public static <T> androidx.collection.ArraySet<T> arraySetOf(T?... values);
+  }
+
+  public final class LongSparseArrayKt {
+    method public static inline operator <T> boolean contains(androidx.collection.LongSparseArray<T>, long key);
+    method public static inline <T> void forEach(androidx.collection.LongSparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Long,? super T,kotlin.Unit> action);
+    method public static inline <T> T! getOrDefault(androidx.collection.LongSparseArray<T>, long key, T? defaultValue);
+    method public static inline <T> T! getOrElse(androidx.collection.LongSparseArray<T>, long key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+    method public static inline <T> int getSize(androidx.collection.LongSparseArray<T>);
+    method public static inline <T> boolean isNotEmpty(androidx.collection.LongSparseArray<T>);
+    method public static <T> kotlin.collections.LongIterator keyIterator(androidx.collection.LongSparseArray<T>);
+    method public static operator <T> androidx.collection.LongSparseArray<T> plus(androidx.collection.LongSparseArray<T>, androidx.collection.LongSparseArray<T> other);
+    method public static inline operator <T> void set(androidx.collection.LongSparseArray<T>, long key, T? value);
+    method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.LongSparseArray<T>);
+  }
+
+  public final class LruCacheKt {
+    method public static inline <K, V> androidx.collection.LruCache<K,V> lruCache(int maxSize, optional kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf, optional kotlin.jvm.functions.Function1<? super K,? extends V> create, optional kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved);
+  }
+
+  public final class SparseArrayKt {
+    method public static inline operator <T> boolean contains(androidx.collection.SparseArrayCompat<T>, int key);
+    method public static inline <T> void forEach(androidx.collection.SparseArrayCompat<T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+    method public static inline <T> T! getOrDefault(androidx.collection.SparseArrayCompat<T>, int key, T? defaultValue);
+    method public static inline <T> T! getOrElse(androidx.collection.SparseArrayCompat<T>, int key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+    method public static inline <T> int getSize(androidx.collection.SparseArrayCompat<T>);
+    method public static inline <T> boolean isNotEmpty(androidx.collection.SparseArrayCompat<T>);
+    method public static <T> kotlin.collections.IntIterator keyIterator(androidx.collection.SparseArrayCompat<T>);
+    method public static operator <T> androidx.collection.SparseArrayCompat<T> plus(androidx.collection.SparseArrayCompat<T>, androidx.collection.SparseArrayCompat<T> other);
+    method public static inline operator <T> void set(androidx.collection.SparseArrayCompat<T>, int key, T? value);
+    method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.SparseArrayCompat<T>);
+  }
+
+}
+
diff --git a/collection/collection-ktx/api/restricted_1.2.0-beta02.txt b/collection/collection-ktx/api/restricted_1.2.0-beta02.txt
new file mode 100644
index 0000000..bca313f
--- /dev/null
+++ b/collection/collection-ktx/api/restricted_1.2.0-beta02.txt
@@ -0,0 +1,45 @@
+// Signature format: 4.0
+package androidx.collection {
+
+  public final class ArrayMapKt {
+    method public static inline <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf();
+    method public static <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf(kotlin.Pair<? extends K,? extends V>... pairs);
+  }
+
+  public final class ArraySetKt {
+    method public static inline <T> androidx.collection.ArraySet<T> arraySetOf();
+    method public static <T> androidx.collection.ArraySet<T> arraySetOf(T?... values);
+  }
+
+  public final class LongSparseArrayKt {
+    method public static inline operator <T> boolean contains(androidx.collection.LongSparseArray<T>, long key);
+    method public static inline <T> void forEach(androidx.collection.LongSparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Long,? super T,kotlin.Unit> action);
+    method public static inline <T> T! getOrDefault(androidx.collection.LongSparseArray<T>, long key, T? defaultValue);
+    method public static inline <T> T! getOrElse(androidx.collection.LongSparseArray<T>, long key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+    method public static inline <T> int getSize(androidx.collection.LongSparseArray<T>);
+    method public static inline <T> boolean isNotEmpty(androidx.collection.LongSparseArray<T>);
+    method public static <T> kotlin.collections.LongIterator keyIterator(androidx.collection.LongSparseArray<T>);
+    method public static operator <T> androidx.collection.LongSparseArray<T> plus(androidx.collection.LongSparseArray<T>, androidx.collection.LongSparseArray<T> other);
+    method public static inline operator <T> void set(androidx.collection.LongSparseArray<T>, long key, T? value);
+    method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.LongSparseArray<T>);
+  }
+
+  public final class LruCacheKt {
+    method public static inline <K, V> androidx.collection.LruCache<K,V> lruCache(int maxSize, optional kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf, optional kotlin.jvm.functions.Function1<? super K,? extends V> create, optional kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved);
+  }
+
+  public final class SparseArrayKt {
+    method public static inline operator <T> boolean contains(androidx.collection.SparseArrayCompat<T>, int key);
+    method public static inline <T> void forEach(androidx.collection.SparseArrayCompat<T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+    method public static inline <T> T! getOrDefault(androidx.collection.SparseArrayCompat<T>, int key, T? defaultValue);
+    method public static inline <T> T! getOrElse(androidx.collection.SparseArrayCompat<T>, int key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+    method public static inline <T> int getSize(androidx.collection.SparseArrayCompat<T>);
+    method public static inline <T> boolean isNotEmpty(androidx.collection.SparseArrayCompat<T>);
+    method public static <T> kotlin.collections.IntIterator keyIterator(androidx.collection.SparseArrayCompat<T>);
+    method public static operator <T> androidx.collection.SparseArrayCompat<T> plus(androidx.collection.SparseArrayCompat<T>, androidx.collection.SparseArrayCompat<T> other);
+    method public static inline operator <T> void set(androidx.collection.SparseArrayCompat<T>, int key, T? value);
+    method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.SparseArrayCompat<T>);
+  }
+
+}
+
diff --git a/collection/collection/api/1.2.0-beta02.txt b/collection/collection/api/1.2.0-beta02.txt
new file mode 100644
index 0000000..fec5af1
--- /dev/null
+++ b/collection/collection/api/1.2.0-beta02.txt
@@ -0,0 +1,184 @@
+// Signature format: 4.0
+package androidx.collection {
+
+  public class ArrayMap<K, V> extends androidx.collection.SimpleArrayMap<K,V> implements java.util.Map<K,V> {
+    ctor public ArrayMap();
+    ctor public ArrayMap(int);
+    ctor public ArrayMap(androidx.collection.SimpleArrayMap!);
+    method public boolean containsAll(java.util.Collection<?>);
+    method public java.util.Set<java.util.Map.Entry<K!,V!>!> entrySet();
+    method public java.util.Set<K!> keySet();
+    method public void putAll(java.util.Map<? extends K,? extends V>);
+    method public boolean removeAll(java.util.Collection<?>);
+    method public boolean retainAll(java.util.Collection<?>);
+    method public java.util.Collection<V!> values();
+  }
+
+  public final class ArraySet<E> implements java.util.Collection<E> java.util.Set<E> {
+    ctor public ArraySet();
+    ctor public ArraySet(int);
+    ctor public ArraySet(androidx.collection.ArraySet<E!>?);
+    ctor public ArraySet(java.util.Collection<E!>?);
+    ctor public ArraySet(E![]?);
+    method public boolean add(E?);
+    method public void addAll(androidx.collection.ArraySet<? extends E>);
+    method public boolean addAll(java.util.Collection<? extends E>);
+    method public void clear();
+    method public boolean contains(Object?);
+    method public boolean containsAll(java.util.Collection<?>);
+    method public void ensureCapacity(int);
+    method public int indexOf(Object?);
+    method public boolean isEmpty();
+    method public java.util.Iterator<E!> iterator();
+    method public boolean remove(Object?);
+    method public boolean removeAll(androidx.collection.ArraySet<? extends E>);
+    method public boolean removeAll(java.util.Collection<?>);
+    method public E! removeAt(int);
+    method public boolean retainAll(java.util.Collection<?>);
+    method public int size();
+    method public Object![] toArray();
+    method public <T> T![] toArray(T![]);
+    method public E! valueAt(int);
+  }
+
+  public final class CircularArray<E> {
+    ctor public CircularArray();
+    ctor public CircularArray(int);
+    method public void addFirst(E!);
+    method public void addLast(E!);
+    method public void clear();
+    method public E! get(int);
+    method public E! getFirst();
+    method public E! getLast();
+    method public boolean isEmpty();
+    method public E! popFirst();
+    method public E! popLast();
+    method public void removeFromEnd(int);
+    method public void removeFromStart(int);
+    method public int size();
+  }
+
+  public final class CircularIntArray {
+    ctor public CircularIntArray();
+    ctor public CircularIntArray(int);
+    method public void addFirst(int);
+    method public void addLast(int);
+    method public void clear();
+    method public int get(int);
+    method public int getFirst();
+    method public int getLast();
+    method public boolean isEmpty();
+    method public int popFirst();
+    method public int popLast();
+    method public void removeFromEnd(int);
+    method public void removeFromStart(int);
+    method public int size();
+  }
+
+  public class LongSparseArray<E> implements java.lang.Cloneable {
+    ctor public LongSparseArray();
+    ctor public LongSparseArray(int);
+    method public void append(long, E!);
+    method public void clear();
+    method public androidx.collection.LongSparseArray<E!>! clone();
+    method public boolean containsKey(long);
+    method public boolean containsValue(E!);
+    method @Deprecated public void delete(long);
+    method public E? get(long);
+    method public E! get(long, E!);
+    method public int indexOfKey(long);
+    method public int indexOfValue(E!);
+    method public boolean isEmpty();
+    method public long keyAt(int);
+    method public void put(long, E!);
+    method public void putAll(androidx.collection.LongSparseArray<? extends E>);
+    method public E? putIfAbsent(long, E!);
+    method public void remove(long);
+    method public boolean remove(long, Object!);
+    method public void removeAt(int);
+    method public E? replace(long, E!);
+    method public boolean replace(long, E!, E!);
+    method public void setValueAt(int, E!);
+    method public int size();
+    method public E! valueAt(int);
+  }
+
+  public class LruCache<K, V> {
+    ctor public LruCache(int);
+    method protected V? create(K);
+    method public final int createCount();
+    method protected void entryRemoved(boolean, K, V, V?);
+    method public final void evictAll();
+    method public final int evictionCount();
+    method public final V? get(K);
+    method public final int hitCount();
+    method public final int maxSize();
+    method public final int missCount();
+    method public final V? put(K, V);
+    method public final int putCount();
+    method public final V? remove(K);
+    method public void resize(int);
+    method public final int size();
+    method protected int sizeOf(K, V);
+    method public final java.util.Map<K!,V!>! snapshot();
+    method public final String toString();
+    method public void trimToSize(int);
+  }
+
+  public class SimpleArrayMap<K, V> {
+    ctor public SimpleArrayMap();
+    ctor public SimpleArrayMap(int);
+    ctor public SimpleArrayMap(androidx.collection.SimpleArrayMap<K!,V!>!);
+    method public void clear();
+    method public boolean containsKey(Object?);
+    method public boolean containsValue(Object!);
+    method public void ensureCapacity(int);
+    method public V? get(Object!);
+    method public V! getOrDefault(Object!, V!);
+    method public int indexOfKey(Object?);
+    method public boolean isEmpty();
+    method public K! keyAt(int);
+    method public V? put(K!, V!);
+    method public void putAll(androidx.collection.SimpleArrayMap<? extends K,? extends V>);
+    method public V? putIfAbsent(K!, V!);
+    method public V? remove(Object!);
+    method public boolean remove(Object!, Object!);
+    method public V! removeAt(int);
+    method public V? replace(K!, V!);
+    method public boolean replace(K!, V!, V!);
+    method public V! setValueAt(int, V!);
+    method public int size();
+    method public V! valueAt(int);
+  }
+
+  public class SparseArrayCompat<E> implements java.lang.Cloneable {
+    ctor public SparseArrayCompat();
+    ctor public SparseArrayCompat(int);
+    method public void append(int, E!);
+    method public void clear();
+    method public androidx.collection.SparseArrayCompat<E!>! clone();
+    method public boolean containsKey(int);
+    method public boolean containsValue(E!);
+    method @Deprecated public void delete(int);
+    method public E? get(int);
+    method public E! get(int, E!);
+    method public int indexOfKey(int);
+    method public int indexOfValue(E!);
+    method public boolean isEmpty();
+    method public int keyAt(int);
+    method public void put(int, E!);
+    method public void putAll(androidx.collection.SparseArrayCompat<? extends E>);
+    method public E? putIfAbsent(int, E!);
+    method public void remove(int);
+    method public boolean remove(int, Object!);
+    method public void removeAt(int);
+    method public void removeAtRange(int, int);
+    method public E? replace(int, E!);
+    method public boolean replace(int, E!, E!);
+    method public void setValueAt(int, E!);
+    method public int size();
+    method public E! valueAt(int);
+  }
+
+}
+
diff --git a/collection/collection/api/public_plus_experimental_1.2.0-beta02.txt b/collection/collection/api/public_plus_experimental_1.2.0-beta02.txt
new file mode 100644
index 0000000..fec5af1
--- /dev/null
+++ b/collection/collection/api/public_plus_experimental_1.2.0-beta02.txt
@@ -0,0 +1,184 @@
+// Signature format: 4.0
+package androidx.collection {
+
+  public class ArrayMap<K, V> extends androidx.collection.SimpleArrayMap<K,V> implements java.util.Map<K,V> {
+    ctor public ArrayMap();
+    ctor public ArrayMap(int);
+    ctor public ArrayMap(androidx.collection.SimpleArrayMap!);
+    method public boolean containsAll(java.util.Collection<?>);
+    method public java.util.Set<java.util.Map.Entry<K!,V!>!> entrySet();
+    method public java.util.Set<K!> keySet();
+    method public void putAll(java.util.Map<? extends K,? extends V>);
+    method public boolean removeAll(java.util.Collection<?>);
+    method public boolean retainAll(java.util.Collection<?>);
+    method public java.util.Collection<V!> values();
+  }
+
+  public final class ArraySet<E> implements java.util.Collection<E> java.util.Set<E> {
+    ctor public ArraySet();
+    ctor public ArraySet(int);
+    ctor public ArraySet(androidx.collection.ArraySet<E!>?);
+    ctor public ArraySet(java.util.Collection<E!>?);
+    ctor public ArraySet(E![]?);
+    method public boolean add(E?);
+    method public void addAll(androidx.collection.ArraySet<? extends E>);
+    method public boolean addAll(java.util.Collection<? extends E>);
+    method public void clear();
+    method public boolean contains(Object?);
+    method public boolean containsAll(java.util.Collection<?>);
+    method public void ensureCapacity(int);
+    method public int indexOf(Object?);
+    method public boolean isEmpty();
+    method public java.util.Iterator<E!> iterator();
+    method public boolean remove(Object?);
+    method public boolean removeAll(androidx.collection.ArraySet<? extends E>);
+    method public boolean removeAll(java.util.Collection<?>);
+    method public E! removeAt(int);
+    method public boolean retainAll(java.util.Collection<?>);
+    method public int size();
+    method public Object![] toArray();
+    method public <T> T![] toArray(T![]);
+    method public E! valueAt(int);
+  }
+
+  public final class CircularArray<E> {
+    ctor public CircularArray();
+    ctor public CircularArray(int);
+    method public void addFirst(E!);
+    method public void addLast(E!);
+    method public void clear();
+    method public E! get(int);
+    method public E! getFirst();
+    method public E! getLast();
+    method public boolean isEmpty();
+    method public E! popFirst();
+    method public E! popLast();
+    method public void removeFromEnd(int);
+    method public void removeFromStart(int);
+    method public int size();
+  }
+
+  public final class CircularIntArray {
+    ctor public CircularIntArray();
+    ctor public CircularIntArray(int);
+    method public void addFirst(int);
+    method public void addLast(int);
+    method public void clear();
+    method public int get(int);
+    method public int getFirst();
+    method public int getLast();
+    method public boolean isEmpty();
+    method public int popFirst();
+    method public int popLast();
+    method public void removeFromEnd(int);
+    method public void removeFromStart(int);
+    method public int size();
+  }
+
+  public class LongSparseArray<E> implements java.lang.Cloneable {
+    ctor public LongSparseArray();
+    ctor public LongSparseArray(int);
+    method public void append(long, E!);
+    method public void clear();
+    method public androidx.collection.LongSparseArray<E!>! clone();
+    method public boolean containsKey(long);
+    method public boolean containsValue(E!);
+    method @Deprecated public void delete(long);
+    method public E? get(long);
+    method public E! get(long, E!);
+    method public int indexOfKey(long);
+    method public int indexOfValue(E!);
+    method public boolean isEmpty();
+    method public long keyAt(int);
+    method public void put(long, E!);
+    method public void putAll(androidx.collection.LongSparseArray<? extends E>);
+    method public E? putIfAbsent(long, E!);
+    method public void remove(long);
+    method public boolean remove(long, Object!);
+    method public void removeAt(int);
+    method public E? replace(long, E!);
+    method public boolean replace(long, E!, E!);
+    method public void setValueAt(int, E!);
+    method public int size();
+    method public E! valueAt(int);
+  }
+
+  public class LruCache<K, V> {
+    ctor public LruCache(int);
+    method protected V? create(K);
+    method public final int createCount();
+    method protected void entryRemoved(boolean, K, V, V?);
+    method public final void evictAll();
+    method public final int evictionCount();
+    method public final V? get(K);
+    method public final int hitCount();
+    method public final int maxSize();
+    method public final int missCount();
+    method public final V? put(K, V);
+    method public final int putCount();
+    method public final V? remove(K);
+    method public void resize(int);
+    method public final int size();
+    method protected int sizeOf(K, V);
+    method public final java.util.Map<K!,V!>! snapshot();
+    method public final String toString();
+    method public void trimToSize(int);
+  }
+
+  public class SimpleArrayMap<K, V> {
+    ctor public SimpleArrayMap();
+    ctor public SimpleArrayMap(int);
+    ctor public SimpleArrayMap(androidx.collection.SimpleArrayMap<K!,V!>!);
+    method public void clear();
+    method public boolean containsKey(Object?);
+    method public boolean containsValue(Object!);
+    method public void ensureCapacity(int);
+    method public V? get(Object!);
+    method public V! getOrDefault(Object!, V!);
+    method public int indexOfKey(Object?);
+    method public boolean isEmpty();
+    method public K! keyAt(int);
+    method public V? put(K!, V!);
+    method public void putAll(androidx.collection.SimpleArrayMap<? extends K,? extends V>);
+    method public V? putIfAbsent(K!, V!);
+    method public V? remove(Object!);
+    method public boolean remove(Object!, Object!);
+    method public V! removeAt(int);
+    method public V? replace(K!, V!);
+    method public boolean replace(K!, V!, V!);
+    method public V! setValueAt(int, V!);
+    method public int size();
+    method public V! valueAt(int);
+  }
+
+  public class SparseArrayCompat<E> implements java.lang.Cloneable {
+    ctor public SparseArrayCompat();
+    ctor public SparseArrayCompat(int);
+    method public void append(int, E!);
+    method public void clear();
+    method public androidx.collection.SparseArrayCompat<E!>! clone();
+    method public boolean containsKey(int);
+    method public boolean containsValue(E!);
+    method @Deprecated public void delete(int);
+    method public E? get(int);
+    method public E! get(int, E!);
+    method public int indexOfKey(int);
+    method public int indexOfValue(E!);
+    method public boolean isEmpty();
+    method public int keyAt(int);
+    method public void put(int, E!);
+    method public void putAll(androidx.collection.SparseArrayCompat<? extends E>);
+    method public E? putIfAbsent(int, E!);
+    method public void remove(int);
+    method public boolean remove(int, Object!);
+    method public void removeAt(int);
+    method public void removeAtRange(int, int);
+    method public E? replace(int, E!);
+    method public boolean replace(int, E!, E!);
+    method public void setValueAt(int, E!);
+    method public int size();
+    method public E! valueAt(int);
+  }
+
+}
+
diff --git a/collection/collection/api/restricted_1.2.0-beta02.txt b/collection/collection/api/restricted_1.2.0-beta02.txt
new file mode 100644
index 0000000..fec5af1
--- /dev/null
+++ b/collection/collection/api/restricted_1.2.0-beta02.txt
@@ -0,0 +1,184 @@
+// Signature format: 4.0
+package androidx.collection {
+
+  public class ArrayMap<K, V> extends androidx.collection.SimpleArrayMap<K,V> implements java.util.Map<K,V> {
+    ctor public ArrayMap();
+    ctor public ArrayMap(int);
+    ctor public ArrayMap(androidx.collection.SimpleArrayMap!);
+    method public boolean containsAll(java.util.Collection<?>);
+    method public java.util.Set<java.util.Map.Entry<K!,V!>!> entrySet();
+    method public java.util.Set<K!> keySet();
+    method public void putAll(java.util.Map<? extends K,? extends V>);
+    method public boolean removeAll(java.util.Collection<?>);
+    method public boolean retainAll(java.util.Collection<?>);
+    method public java.util.Collection<V!> values();
+  }
+
+  public final class ArraySet<E> implements java.util.Collection<E> java.util.Set<E> {
+    ctor public ArraySet();
+    ctor public ArraySet(int);
+    ctor public ArraySet(androidx.collection.ArraySet<E!>?);
+    ctor public ArraySet(java.util.Collection<E!>?);
+    ctor public ArraySet(E![]?);
+    method public boolean add(E?);
+    method public void addAll(androidx.collection.ArraySet<? extends E>);
+    method public boolean addAll(java.util.Collection<? extends E>);
+    method public void clear();
+    method public boolean contains(Object?);
+    method public boolean containsAll(java.util.Collection<?>);
+    method public void ensureCapacity(int);
+    method public int indexOf(Object?);
+    method public boolean isEmpty();
+    method public java.util.Iterator<E!> iterator();
+    method public boolean remove(Object?);
+    method public boolean removeAll(androidx.collection.ArraySet<? extends E>);
+    method public boolean removeAll(java.util.Collection<?>);
+    method public E! removeAt(int);
+    method public boolean retainAll(java.util.Collection<?>);
+    method public int size();
+    method public Object![] toArray();
+    method public <T> T![] toArray(T![]);
+    method public E! valueAt(int);
+  }
+
+  public final class CircularArray<E> {
+    ctor public CircularArray();
+    ctor public CircularArray(int);
+    method public void addFirst(E!);
+    method public void addLast(E!);
+    method public void clear();
+    method public E! get(int);
+    method public E! getFirst();
+    method public E! getLast();
+    method public boolean isEmpty();
+    method public E! popFirst();
+    method public E! popLast();
+    method public void removeFromEnd(int);
+    method public void removeFromStart(int);
+    method public int size();
+  }
+
+  public final class CircularIntArray {
+    ctor public CircularIntArray();
+    ctor public CircularIntArray(int);
+    method public void addFirst(int);
+    method public void addLast(int);
+    method public void clear();
+    method public int get(int);
+    method public int getFirst();
+    method public int getLast();
+    method public boolean isEmpty();
+    method public int popFirst();
+    method public int popLast();
+    method public void removeFromEnd(int);
+    method public void removeFromStart(int);
+    method public int size();
+  }
+
+  public class LongSparseArray<E> implements java.lang.Cloneable {
+    ctor public LongSparseArray();
+    ctor public LongSparseArray(int);
+    method public void append(long, E!);
+    method public void clear();
+    method public androidx.collection.LongSparseArray<E!>! clone();
+    method public boolean containsKey(long);
+    method public boolean containsValue(E!);
+    method @Deprecated public void delete(long);
+    method public E? get(long);
+    method public E! get(long, E!);
+    method public int indexOfKey(long);
+    method public int indexOfValue(E!);
+    method public boolean isEmpty();
+    method public long keyAt(int);
+    method public void put(long, E!);
+    method public void putAll(androidx.collection.LongSparseArray<? extends E>);
+    method public E? putIfAbsent(long, E!);
+    method public void remove(long);
+    method public boolean remove(long, Object!);
+    method public void removeAt(int);
+    method public E? replace(long, E!);
+    method public boolean replace(long, E!, E!);
+    method public void setValueAt(int, E!);
+    method public int size();
+    method public E! valueAt(int);
+  }
+
+  public class LruCache<K, V> {
+    ctor public LruCache(int);
+    method protected V? create(K);
+    method public final int createCount();
+    method protected void entryRemoved(boolean, K, V, V?);
+    method public final void evictAll();
+    method public final int evictionCount();
+    method public final V? get(K);
+    method public final int hitCount();
+    method public final int maxSize();
+    method public final int missCount();
+    method public final V? put(K, V);
+    method public final int putCount();
+    method public final V? remove(K);
+    method public void resize(int);
+    method public final int size();
+    method protected int sizeOf(K, V);
+    method public final java.util.Map<K!,V!>! snapshot();
+    method public final String toString();
+    method public void trimToSize(int);
+  }
+
+  public class SimpleArrayMap<K, V> {
+    ctor public SimpleArrayMap();
+    ctor public SimpleArrayMap(int);
+    ctor public SimpleArrayMap(androidx.collection.SimpleArrayMap<K!,V!>!);
+    method public void clear();
+    method public boolean containsKey(Object?);
+    method public boolean containsValue(Object!);
+    method public void ensureCapacity(int);
+    method public V? get(Object!);
+    method public V! getOrDefault(Object!, V!);
+    method public int indexOfKey(Object?);
+    method public boolean isEmpty();
+    method public K! keyAt(int);
+    method public V? put(K!, V!);
+    method public void putAll(androidx.collection.SimpleArrayMap<? extends K,? extends V>);
+    method public V? putIfAbsent(K!, V!);
+    method public V? remove(Object!);
+    method public boolean remove(Object!, Object!);
+    method public V! removeAt(int);
+    method public V? replace(K!, V!);
+    method public boolean replace(K!, V!, V!);
+    method public V! setValueAt(int, V!);
+    method public int size();
+    method public V! valueAt(int);
+  }
+
+  public class SparseArrayCompat<E> implements java.lang.Cloneable {
+    ctor public SparseArrayCompat();
+    ctor public SparseArrayCompat(int);
+    method public void append(int, E!);
+    method public void clear();
+    method public androidx.collection.SparseArrayCompat<E!>! clone();
+    method public boolean containsKey(int);
+    method public boolean containsValue(E!);
+    method @Deprecated public void delete(int);
+    method public E? get(int);
+    method public E! get(int, E!);
+    method public int indexOfKey(int);
+    method public int indexOfValue(E!);
+    method public boolean isEmpty();
+    method public int keyAt(int);
+    method public void put(int, E!);
+    method public void putAll(androidx.collection.SparseArrayCompat<? extends E>);
+    method public E? putIfAbsent(int, E!);
+    method public void remove(int);
+    method public boolean remove(int, Object!);
+    method public void removeAt(int);
+    method public void removeAtRange(int, int);
+    method public E? replace(int, E!);
+    method public boolean replace(int, E!, E!);
+    method public void setValueAt(int, E!);
+    method public int size();
+    method public E! valueAt(int);
+  }
+
+}
+
diff --git a/collection2/collection2/build.gradle b/collection2/collection2/build.gradle
index 3e55350..69a11cb 100644
--- a/collection2/collection2/build.gradle
+++ b/collection2/collection2/build.gradle
@@ -30,6 +30,7 @@
 
 plugins {
     id("AndroidXPlugin")
+    id("java") // https://github.com/gradle/gradle/issues/18622
     id("org.jetbrains.kotlin.multiplatform")
     id("me.champeau.gradle.japicmp")
 }
diff --git a/compose/animation/animation-graphics/src/commonMain/kotlin/androidx/compose/animation/graphics/vector/AnimatedImageVector.kt b/compose/animation/animation-graphics/src/commonMain/kotlin/androidx/compose/animation/graphics/vector/AnimatedImageVector.kt
index c688f19..57d7abd 100644
--- a/compose/animation/animation-graphics/src/commonMain/kotlin/androidx/compose/animation/graphics/vector/AnimatedImageVector.kt
+++ b/compose/animation/animation-graphics/src/commonMain/kotlin/androidx/compose/animation/graphics/vector/AnimatedImageVector.kt
@@ -20,7 +20,6 @@
 import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.graphics.vector.RenderVectorGroup
@@ -63,7 +62,6 @@
      *
      * @sample androidx.compose.animation.graphics.samples.AnimatedVectorSample
      */
-    @OptIn(ExperimentalComposeUiApi::class)
     @Composable
     fun painterFor(atEnd: Boolean): Painter {
         return painterFor(atEnd) { group, overrides ->
@@ -71,7 +69,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     @Composable
     internal fun painterFor(
         atEnd: Boolean,
diff --git a/compose/animation/animation/api/public_plus_experimental_current.txt b/compose/animation/animation/api/public_plus_experimental_current.txt
index 5c15629..e9fef24 100644
--- a/compose/animation/animation/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation/api/public_plus_experimental_current.txt
@@ -7,7 +7,7 @@
 
   public final class AnimatedContentKt {
     method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <S> void AnimatedContent(S? targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <S> void AnimatedContent(androidx.compose.animation.core.Transition<S>, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <S> void AnimatedContent(androidx.compose.animation.core.Transition<S>, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super S,?> contentKey, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
     method @androidx.compose.animation.ExperimentalAnimationApi public static androidx.compose.animation.SizeTransform SizeTransform(optional boolean clip, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.IntSize,? super androidx.compose.ui.unit.IntSize,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize>> sizeAnimationSpec);
     method @androidx.compose.animation.ExperimentalAnimationApi public static infix androidx.compose.animation.ContentTransform with(androidx.compose.animation.EnterTransition, androidx.compose.animation.ExitTransition exit);
   }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedContentWithContentKeyDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedContentWithContentKeyDemo.kt
new file mode 100644
index 0000000..0894882
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedContentWithContentKeyDemo.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.animation.demos
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+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.saveable.rememberSaveableStateHolder
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+fun AnimatedContentWithContentKeyDemo() {
+    val model: ScreenModel = remember { ScreenModel() }
+    val transition = updateTransition(model.target)
+    Box {
+        val holder = rememberSaveableStateHolder()
+        transition.AnimatedContent(
+            Modifier.clickable { model.toggleTarget() },
+            contentAlignment = Alignment.Center,
+            contentKey = { it.type }
+        ) {
+            if (it.type == MyScreen.Type.Count) {
+                holder.SaveableStateProvider(it.type) {
+                    var count by rememberSaveable { mutableStateOf(0) }
+                    Column(
+                        Modifier.fillMaxSize(),
+                        Arrangement.Center,
+                        Alignment.CenterHorizontally
+                    ) {
+                        Button(onClick = { count++ }) {
+                            Text("+1")
+                        }
+                        Spacer(Modifier.size(20.dp))
+                        Text("Count: $count", fontSize = 20.sp)
+                    }
+                }
+            } else {
+                Box(Modifier.fillMaxSize().background(Color.LightGray))
+            }
+        }
+        Text(
+            "Tap anywhere to change content.\n Current content: ${model.target.type}",
+            Modifier.align(Alignment.BottomCenter)
+        )
+    }
+}
+
+sealed class MyScreen {
+    enum class Type { Count, Blank }
+
+    abstract val type: Type
+}
+
+class CountScreen : MyScreen() {
+    override val type = Type.Count
+}
+
+class BlankScreen : MyScreen() {
+    override val type = Type.Blank
+}
+
+class ScreenModel {
+    fun toggleTarget() {
+        if (target.type == MyScreen.Type.Count) {
+            target = BlankScreen()
+        } else {
+            target = CountScreen()
+        }
+    }
+
+    var target: MyScreen by mutableStateOf(CountScreen())
+        private set
+}
\ No newline at end of file
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
index ffa354f..5a5c306 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
@@ -44,6 +44,9 @@
                 ComposableDemo("Cross Fade") { CrossfadeDemo() },
                 ComposableDemo("Modifier.animateEnterExit Demo") { AnimateEnterExitDemo() },
                 ComposableDemo("Nested Menu") { NestedMenuDemo() },
+                ComposableDemo("Save/Restore in AnimatedContent") {
+                    AnimatedContentWithContentKeyDemo()
+                },
                 ComposableDemo("Scaled Enter/Exit") { ScaleEnterExitDemo() },
                 ComposableDemo("Shrine Cart") { ShrineCartDemo() },
                 ComposableDemo("Screen Transition") { ScreenTransitionDemo() },
diff --git a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
index 9584821..094b975 100644
--- a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
+++ b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
@@ -29,7 +29,10 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.saveable.rememberSaveableStateHolder
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameMillis
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -47,6 +50,7 @@
 import androidx.test.filters.LargeTest
 import kotlinx.coroutines.delay
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Rule
 import org.junit.Test
@@ -385,4 +389,50 @@
             }
         }
     }
+
+    @OptIn(ExperimentalAnimationApi::class)
+    @Test
+    fun AnimatedContentWithKeysTest() {
+        var targetState by mutableStateOf(1)
+        val list = mutableListOf<Int>()
+        rule.setContent {
+            val transition = updateTransition(targetState)
+            val holder = rememberSaveableStateHolder()
+            transition.AnimatedContent(contentKey = { it > 2 }) {
+                if (it <= 2) {
+                    holder.SaveableStateProvider(11) {
+                        var count by rememberSaveable { mutableStateOf(0) }
+                        LaunchedEffect(Unit) {
+                            list.add(++count)
+                        }
+                    }
+                }
+                Box(Modifier.requiredSize(200.dp))
+            }
+            LaunchedEffect(Unit) {
+                assertFalse(transition.isRunning)
+                targetState = 2
+                withFrameMillis { }
+                assertFalse(transition.isRunning)
+                assertEquals(transition.currentState, transition.targetState)
+                // This state change should now cause an animation
+                targetState = 3
+                withFrameMillis { }
+                assertTrue(transition.isRunning)
+            }
+        }
+        rule.waitForIdle()
+        rule.runOnIdle {
+            assertEquals(1, list.size)
+            assertEquals(1, list[0])
+            targetState = 1
+        }
+
+        rule.runOnIdle {
+            // Check that save worked
+            assertEquals(2, list.size)
+            assertEquals(1, list[0])
+            assertEquals(2, list[1])
+        }
+    }
 }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
index a3c6f32..dd5a571 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
@@ -132,7 +132,7 @@
         modifier,
         transitionSpec,
         contentAlignment,
-        content
+        content = content
     )
 }
 
@@ -559,6 +559,12 @@
  * via [AnimatedVisibilityScope.animateEnterExit] and [AnimatedVisibilityScope.transition]. These
  * custom enter/exit animations will be triggered as the content enters/leaves the container.
  *
+ * [contentKey] can be used to specify a key for each targetState. There will be no animation
+ * when switching between target states that share the same same key. By default,
+ * the key will be the same as the targetState object. [contentKey] can be particularly useful if
+ * target state object gets recreated across save & restore while a more persistent key is needed
+ * to properly restore the internal states of the content.
+ *
  * @sample androidx.compose.animation.samples.TransitionExtensionAnimatedContentSample
  *
  * @see ContentTransform
@@ -574,6 +580,7 @@
             fadeOut(animationSpec = tween(90))
     },
     contentAlignment: Alignment = Alignment.TopStart,
+    contentKey: (targetState: S) -> Any? = { it },
     content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit
 ) {
     val layoutDirection = LocalLayoutDirection.current
@@ -603,10 +610,17 @@
     // the targetState get placed last, so the target composable will be displayed on top of
     // content associated with other states, unless zIndex is specified.
     if (currentState != targetState && !currentlyVisible.contains(targetState)) {
-        currentlyVisible.add(targetState)
+        // Replace the target with the same key if any
+        val id = currentlyVisible.indexOfFirst { contentKey(it) == contentKey(targetState) }
+        if (id == -1) {
+            currentlyVisible.add(targetState)
+        } else {
+            currentlyVisible[id] = targetState
+        }
     }
 
     if (!contentMap.containsKey(targetState)) {
+        contentMap.clear()
         currentlyVisible.fastForEach { stateForContent ->
             contentMap[stateForContent] = {
                 val specOnEnter = remember { transitionSpec(rootScope) }
@@ -653,7 +667,7 @@
         modifier = modifier.then(sizeModifier),
         content = {
             currentlyVisible.forEach {
-                key(it) {
+                key(contentKey(it)) {
                     contentMap[it]?.invoke()
                 }
             }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
index cef6acf..73a6146 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
@@ -56,6 +56,7 @@
             4300 to "1.1.0-alpha04",
             4400 to "1.1.0-alpha05",
             4500 to "1.1.0-alpha06",
+            4600 to "1.1.0-alpha07",
         )
 
         /**
@@ -68,7 +69,7 @@
          * The maven version string of this compiler. This string should be updated before/after every
          * release.
          */
-        const val compilerVersion: String = "1.1.0-alpha06"
+        const val compilerVersion: String = "1.1.0-alpha07"
         private val minimumRuntimeVersion: String
             get() = versionTable[minimumRuntimeVersionInt] ?: "unknown"
     }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt
index 7478f64..64d3b6f 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeVariousInputField.kt
@@ -24,6 +24,7 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.samples.CreditCardSample
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material.Text
@@ -46,45 +47,6 @@
 import androidx.compose.ui.text.toUpperCase
 
 /**
- * The offset translator used for credit card input field.
- *
- * @see creditCardFilter
- */
-private val creditCardOffsetTranslator = object : OffsetMapping {
-    override fun originalToTransformed(offset: Int): Int {
-        if (offset <= 3) return offset
-        if (offset <= 7) return offset + 1
-        if (offset <= 11) return offset + 2
-        if (offset <= 16) return offset + 3
-        return 19
-    }
-
-    override fun transformedToOriginal(offset: Int): Int {
-        if (offset <= 4) return offset
-        if (offset <= 9) return offset - 1
-        if (offset <= 14) return offset - 2
-        if (offset <= 19) return offset - 3
-        return 16
-    }
-}
-
-/**
- * The visual filter for credit card input field.
- *
- * This filter converts up to 16 digits to hyphen connected 4 digits string.
- * For example, "1234567890123456" will be shown as "1234-5678-9012-3456".
- */
-private val creditCardFilter = VisualTransformation { text ->
-    val trimmed = if (text.text.length >= 16) text.text.substring(0..15) else text.text
-    var out = ""
-    for (i in 0 until trimmed.length) {
-        out += trimmed[i]
-        if (i % 4 == 3 && i != 15) out += "-"
-    }
-    TransformedText(AnnotatedString(out), creditCardOffsetTranslator)
-}
-
-/**
  * The offset translator which works for all offset keep remains the same.
  */
 private val identityTranslator = object : OffsetMapping {
@@ -233,13 +195,7 @@
         }
         item {
             TagLine(tag = "Credit Card")
-            VariousEditLine(
-                keyboardType = KeyboardType.Number,
-                onValueChange = { old, new ->
-                    if (new.length > 16 || new.any { !it.isDigit() }) old else new
-                },
-                visualTransformation = creditCardFilter
-            )
+            CreditCardSample()
         }
         item {
             TagLine(tag = "Email Suggestion")
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
index de4e98c3..643dbb6 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
@@ -23,9 +23,12 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material.Icon
 import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
@@ -37,7 +40,12 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.OffsetMapping
 import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.input.TransformedText
+import androidx.compose.ui.text.input.VisualTransformation
 import androidx.compose.ui.unit.dp
 
 @Sampled
@@ -110,4 +118,59 @@
             }
         }
     )
+}
+
+@Sampled
+@Composable
+fun CreditCardSample() {
+    /** The offset translator used for credit card input field */
+    val creditCardOffsetTranslator = object : OffsetMapping {
+        override fun originalToTransformed(offset: Int): Int {
+            return when {
+                offset < 4 -> offset
+                offset < 8 -> offset + 1
+                offset < 12 -> offset + 2
+                offset <= 16 -> offset + 3
+                else -> 19
+            }
+        }
+
+        override fun transformedToOriginal(offset: Int): Int {
+            return when {
+                offset <= 4 -> offset
+                offset <= 9 -> offset - 1
+                offset <= 14 -> offset - 2
+                offset <= 19 -> offset - 3
+                else -> 16
+            }
+        }
+    }
+
+    /**
+     * Converts up to 16 digits to hyphen connected 4 digits string. For example,
+     * "1234567890123456" will be shown as "1234-5678-9012-3456"
+     */
+    val creditCardTransformation = VisualTransformation { text ->
+        val trimmedText = if (text.text.length > 16) text.text.substring(0..15) else text.text
+        var transformedText = ""
+        trimmedText.forEachIndexed { index, char ->
+            transformedText += char
+            if ((index + 1) % 4 == 0 && index != 15) transformedText += "-"
+        }
+        TransformedText(AnnotatedString(transformedText), creditCardOffsetTranslator)
+    }
+
+    var text by rememberSaveable { mutableStateOf("") }
+    BasicTextField(
+        value = text,
+        onValueChange = { input ->
+            if (input.length <= 16 && input.none { !it.isDigit() }) {
+                text = input
+            }
+        },
+        modifier = Modifier.size(170.dp, 30.dp).background(Color.LightGray).wrapContentSize(),
+        singleLine = true,
+        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
+        visualTransformation = creditCardTransformation
+    )
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt
index 2f8b9d6..745c482 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableTest.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.gestures.draggable
 import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -29,16 +30,25 @@
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.InputMode
+import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -434,7 +444,7 @@
     fun clickableTest_interactionSource() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -455,7 +465,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -499,7 +509,7 @@
     fun clickableTest_interactionSource_immediateRelease() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -520,7 +530,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -549,7 +559,7 @@
     fun clickableTest_interactionSource_immediateCancel() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -570,7 +580,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -595,7 +605,7 @@
     fun clickableTest_interactionSource_immediateDrag() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -620,7 +630,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -646,7 +656,7 @@
     fun clickableTest_interactionSource_dragAfterTimeout() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -671,7 +681,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -710,7 +720,7 @@
     fun clickableTest_interactionSource_cancelledGesture() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -731,7 +741,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -766,7 +776,7 @@
         val interactionSource = MutableInteractionSource()
         var emitClickableText by mutableStateOf(true)
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -789,7 +799,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -828,7 +838,7 @@
     fun clickableTest_interactionSource_hover() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.setContent {
             scope = rememberCoroutineScope()
@@ -847,7 +857,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -881,7 +891,7 @@
     fun clickableTest_interactionSource_hover_and_press() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.setContent {
             scope = rememberCoroutineScope()
@@ -900,7 +910,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -928,6 +938,123 @@
         }
     }
 
+    @Test
+    fun clickableTest_interactionSource_focus_inTouchMode() {
+        val interactionSource = MutableInteractionSource()
+
+        lateinit var scope: CoroutineScope
+
+        val focusRequester = FocusRequester()
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box {
+                BasicText(
+                    "ClickableText",
+                    modifier = Modifier
+                        .testTag("myClickable")
+                        .focusRequester(focusRequester)
+                        .combinedClickable(
+                            interactionSource = interactionSource,
+                            indication = null
+                        ) {}
+                )
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        // Touch mode by default, so we shouldn't be focused
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+    }
+
+    @Test
+    fun clickableTest_interactionSource_focus_inKeyboardMode() {
+        val interactionSource = MutableInteractionSource()
+
+        lateinit var scope: CoroutineScope
+
+        val focusRequester = FocusRequester()
+
+        lateinit var focusManager: FocusManager
+
+        val keyboardInputModeManager = object : InputModeManager {
+            override val inputMode = InputMode.Keyboard
+
+            @OptIn(ExperimentalComposeUiApi::class)
+            override fun requestInputMode(inputMode: InputMode) = true
+        }
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            focusManager = LocalFocusManager.current
+            CompositionLocalProvider(LocalInputModeManager provides keyboardInputModeManager) {
+                Box {
+                    BasicText(
+                        "ClickableText",
+                        modifier = Modifier
+                            .testTag("myClickable")
+                            .focusRequester(focusRequester)
+                            .combinedClickable(
+                                interactionSource = interactionSource,
+                                indication = null
+                            ) {}
+                    )
+                }
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        // Keyboard mode, so we should now be focused and see an interaction
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(1)
+            assertThat(interactions.first()).isInstanceOf(FocusInteraction.Focus::class.java)
+        }
+
+        rule.runOnIdle {
+            focusManager.clearFocus()
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(2)
+            assertThat(interactions.first()).isInstanceOf(FocusInteraction.Focus::class.java)
+            assertThat(interactions[1])
+                .isInstanceOf(FocusInteraction.Unfocus::class.java)
+            assertThat((interactions[1] as FocusInteraction.Unfocus).focus)
+                .isEqualTo(interactions[0])
+        }
+    }
+
+    // TODO: b/202871171 - add test for changing between keyboard mode and touch mode, making sure
+    // it resets existing focus
+
     /**
      * Regression test for b/186223077
      *
@@ -945,7 +1072,7 @@
         // Simulate the long click causing a recomposition, and changing the lambda instance
         onLongClick = initialLongClick
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -967,7 +1094,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -1021,7 +1148,7 @@
         // Simulate the long click causing a recomposition, and changing the lambda to be null
         onLongClick = initialLongClick
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -1043,7 +1170,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt
index b74d3a8..cf783e1 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/SelectableTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation
 
+import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -23,15 +24,24 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.testutils.first
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.InputMode
+import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsProperties
@@ -180,7 +190,7 @@
     fun selectableTest_interactionSource() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -202,7 +212,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -238,7 +248,7 @@
         val interactionSource = MutableInteractionSource()
         var emitSelectableText by mutableStateOf(true)
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -262,7 +272,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -302,7 +312,7 @@
     fun selectableTest_interactionSource_hover() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.setContent {
             scope = rememberCoroutineScope()
@@ -322,7 +332,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -352,6 +362,127 @@
     }
 
     @Test
+    fun selectableTest_interactionSource_focus_inTouchMode() {
+        val interactionSource = MutableInteractionSource()
+
+        lateinit var scope: CoroutineScope
+
+        val focusRequester = FocusRequester()
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box {
+                Box(
+                    Modifier
+                        .focusRequester(focusRequester)
+                        .selectable(
+                            selected = true,
+                            interactionSource = interactionSource,
+                            indication = null,
+                            onClick = {}
+                        )
+                ) {
+                    BasicText("SelectableText")
+                }
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        // Touch mode by default, so we shouldn't be focused
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+    }
+
+    @Test
+    fun selectableTest_interactionSource_focus_inKeyboardMode() {
+        val interactionSource = MutableInteractionSource()
+
+        lateinit var scope: CoroutineScope
+
+        val focusRequester = FocusRequester()
+
+        lateinit var focusManager: FocusManager
+
+        val keyboardInputModeManager = object : InputModeManager {
+            override val inputMode = InputMode.Keyboard
+
+            @OptIn(ExperimentalComposeUiApi::class)
+            override fun requestInputMode(inputMode: InputMode) = true
+        }
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            focusManager = LocalFocusManager.current
+            CompositionLocalProvider(LocalInputModeManager provides keyboardInputModeManager) {
+                Box {
+                    Box(
+                        Modifier
+                            .focusRequester(focusRequester)
+                            .selectable(
+                                selected = true,
+                                interactionSource = interactionSource,
+                                indication = null,
+                                onClick = {}
+                            )
+                    ) {
+                        BasicText("SelectableText")
+                    }
+                }
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        // Keyboard mode, so we should now be focused and see an interaction
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(1)
+            assertThat(interactions.first()).isInstanceOf(FocusInteraction.Focus::class.java)
+        }
+
+        rule.runOnIdle {
+            focusManager.clearFocus()
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(2)
+            assertThat(interactions.first()).isInstanceOf(FocusInteraction.Focus::class.java)
+            assertThat(interactions[1])
+                .isInstanceOf(FocusInteraction.Unfocus::class.java)
+            assertThat((interactions[1] as FocusInteraction.Unfocus).focus)
+                .isEqualTo(interactions[0])
+        }
+    }
+
+    // TODO: b/202871171 - add test for changing between keyboard mode and touch mode, making sure
+    // it resets existing focus
+
+    @Test
     fun selectableTest_testInspectorValue_noIndication() {
         rule.setContent {
             val modifier = Modifier.selectable(false) {} as InspectableValue
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt
index 3de6204..a01e6a7 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ToggleableTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation
 
+import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -27,15 +28,24 @@
 import androidx.compose.foundation.selection.toggleable
 import androidx.compose.foundation.selection.triStateToggleable
 import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.testutils.first
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.InputMode
+import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.platform.InspectableValue
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsProperties
@@ -275,7 +285,7 @@
     fun toggleableTest_interactionSource() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -297,7 +307,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -333,7 +343,7 @@
         val interactionSource = MutableInteractionSource()
         var emitToggleableText by mutableStateOf(true)
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.mainClock.autoAdvance = false
 
@@ -357,7 +367,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -397,7 +407,7 @@
     fun toggleableTest_interactionSource_hover() {
         val interactionSource = MutableInteractionSource()
 
-        var scope: CoroutineScope? = null
+        lateinit var scope: CoroutineScope
 
         rule.setContent {
             scope = rememberCoroutineScope()
@@ -417,7 +427,7 @@
 
         val interactions = mutableListOf<Interaction>()
 
-        scope!!.launch {
+        scope.launch {
             interactionSource.interactions.collect { interactions.add(it) }
         }
 
@@ -447,6 +457,127 @@
     }
 
     @Test
+    fun toggleableTest_interactionSource_focus_inTouchMode() {
+        val interactionSource = MutableInteractionSource()
+
+        lateinit var scope: CoroutineScope
+
+        val focusRequester = FocusRequester()
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box {
+                Box(
+                    Modifier
+                        .focusRequester(focusRequester)
+                        .toggleable(
+                            value = true,
+                            interactionSource = interactionSource,
+                            indication = null,
+                            onValueChange = {}
+                        )
+                ) {
+                    BasicText("ToggleableText")
+                }
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        // Touch mode by default, so we shouldn't be focused
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+    }
+
+    @Test
+    fun toggleableTest_interactionSource_focus_inKeyboardMode() {
+        val interactionSource = MutableInteractionSource()
+
+        lateinit var scope: CoroutineScope
+
+        val focusRequester = FocusRequester()
+
+        lateinit var focusManager: FocusManager
+
+        val keyboardInputModeManager = object : InputModeManager {
+            override val inputMode = InputMode.Keyboard
+
+            @OptIn(ExperimentalComposeUiApi::class)
+            override fun requestInputMode(inputMode: InputMode) = true
+        }
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            focusManager = LocalFocusManager.current
+            CompositionLocalProvider(LocalInputModeManager provides keyboardInputModeManager) {
+                Box {
+                    Box(
+                        Modifier
+                            .focusRequester(focusRequester)
+                            .toggleable(
+                                value = true,
+                                interactionSource = interactionSource,
+                                indication = null,
+                                onValueChange = {}
+                            )
+                    ) {
+                        BasicText("ToggleableText")
+                    }
+                }
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        // Keyboard mode, so we should now be focused and see an interaction
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(1)
+            assertThat(interactions.first()).isInstanceOf(FocusInteraction.Focus::class.java)
+        }
+
+        rule.runOnIdle {
+            focusManager.clearFocus()
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(2)
+            assertThat(interactions.first()).isInstanceOf(FocusInteraction.Focus::class.java)
+            assertThat(interactions[1])
+                .isInstanceOf(FocusInteraction.Unfocus::class.java)
+            assertThat((interactions[1] as FocusInteraction.Unfocus).focus)
+                .isEqualTo(interactions[0])
+        }
+    }
+
+    // TODO: b/202871171 - add test for changing between keyboard mode and touch mode, making sure
+    // it resets existing focus
+
+    @Test
     fun toggleableText_testInspectorValue_noIndication() {
         rule.setContent {
             val modifier = Modifier.toggleable(value = true, onValueChange = {}) as InspectableValue
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index e752677..83b26bf 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -394,8 +394,6 @@
  */
 internal expect val TapIndicationDelay: Long
 
-@Composable
-@Suppress("ComposableModifierFactory")
 internal fun Modifier.genericClickableWithoutGesture(
     gestureModifiers: Modifier,
     interactionSource: MutableInteractionSource,
@@ -426,6 +424,7 @@
     return this
         .then(semanticModifier)
         .indication(interactionSource, indication)
-        .hoverable(interactionSource = interactionSource)
+        .hoverable(enabled = enabled, interactionSource = interactionSource)
+        .focusableInNonTouchMode(enabled = enabled, interactionSource = interactionSource)
         .then(gestureModifiers)
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
index 18786ba..582e52c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
@@ -27,10 +27,13 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
+import androidx.compose.ui.focus.focusProperties
 import androidx.compose.ui.focus.focusTarget
 import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.input.InputMode
 import androidx.compose.ui.layout.RelocationRequester
 import androidx.compose.ui.layout.relocationRequester
+import androidx.compose.ui.platform.LocalInputModeManager
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.focused
 import androidx.compose.ui.semantics.semantics
@@ -122,3 +125,25 @@
         Modifier
     }
 }
+
+// TODO: b/202856230 - consider either making this / a similar API public, or add a parameter to
+//  focusable to configure this behavior.
+/**
+ * [focusable] but only when not in touch mode - when [LocalInputModeManager] is
+ * not [InputMode.Touch]
+ */
+internal fun Modifier.focusableInNonTouchMode(
+    enabled: Boolean,
+    interactionSource: MutableInteractionSource?
+) = composed(
+    inspectorInfo = debugInspectorInfo {
+        name = "focusableInNonTouchMode"
+        properties["enabled"] = enabled
+        properties["interactionSource"] = interactionSource
+    }
+) {
+    val inputModeManager = LocalInputModeManager.current
+    Modifier
+        .focusProperties { canFocus = inputModeManager.inputMode != InputMode.Touch }
+        .focusable(enabled, interactionSource)
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Indication.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Indication.kt
index d85462d..e15bfe4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Indication.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Indication.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation
 
 import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.collectIsFocusedAsState
 import androidx.compose.foundation.interaction.collectIsHoveredAsState
 import androidx.compose.foundation.interaction.collectIsPressedAsState
 import androidx.compose.runtime.Composable
@@ -149,13 +150,14 @@
 
     private class DefaultDebugIndicationInstance(
         private val isPressed: State<Boolean>,
-        private val isHovered: State<Boolean>
+        private val isHovered: State<Boolean>,
+        private val isFocused: State<Boolean>,
     ) : IndicationInstance {
         override fun ContentDrawScope.drawIndication() {
             drawContent()
             if (isPressed.value) {
                 drawRect(color = Color.Black.copy(alpha = 0.3f), size = size)
-            } else if (isHovered.value) {
+            } else if (isHovered.value || isFocused.value) {
                 drawRect(color = Color.Black.copy(alpha = 0.1f), size = size)
             }
         }
@@ -165,8 +167,9 @@
     override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
         val isPressed = interactionSource.collectIsPressedAsState()
         val isHovered = interactionSource.collectIsHoveredAsState()
+        val isFocused = interactionSource.collectIsFocusedAsState()
         return remember(interactionSource) {
-            DefaultDebugIndicationInstance(isPressed, isHovered)
+            DefaultDebugIndicationInstance(isPressed, isHovered, isFocused)
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ProgressSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ProgressSemantics.kt
index e6ad938..c4bc989 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ProgressSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ProgressSemantics.kt
@@ -44,7 +44,10 @@
     /*@IntRange(from = 0)*/
     steps: Int = 0
 ): Modifier {
-    return semantics {
+    // Older versions of Talkback will ignore nodes with range info which aren't focusable or
+    // screen reader focusable. Setting this semantics as merging descendants will mark it as
+    // screen reader focusable.
+    return semantics(mergeDescendants = true) {
         progressBarRangeInfo =
             ProgressBarRangeInfo(value.coerceIn(valueRange), valueRange, steps)
     }
@@ -62,7 +65,10 @@
  */
 @Stable
 fun Modifier.progressSemantics(): Modifier {
-    return semantics {
+    // Older versions of Talkback will ignore nodes with range info which aren't focusable or
+    // screen reader focusable. Setting this semantics as merging descendants will mark it as
+    // screen reader focusable.
+    return semantics(mergeDescendants = true) {
         progressBarRangeInfo = ProgressBarRangeInfo.Indeterminate
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
index 8d79b16..d954661 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.Indication
 import androidx.compose.foundation.PressedInteractionSourceDisposableEffect
 import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.focusableInNonTouchMode
 import androidx.compose.foundation.gestures.detectTapAndPress
 import androidx.compose.foundation.handlePressInteraction
 import androidx.compose.foundation.hoverable
@@ -269,6 +270,7 @@
     this
         .then(semantics)
         .indication(interactionSource, indication)
-        .hoverable(interactionSource = interactionSource)
+        .hoverable(enabled = enabled, interactionSource = interactionSource)
+        .focusableInNonTouchMode(enabled = enabled, interactionSource = interactionSource)
         .then(gestures)
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
index f07ef42..4025dda 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
@@ -71,6 +71,11 @@
  * hit target area, use the decoration box:
  * @sample androidx.compose.foundation.samples.TextFieldWithIconSample
  *
+ * In order to create formatted text field, for example for entering a phone number or a social
+ * security number, use a [visualTransformation] parameter. Below is the example of the text field
+ * for entering a credit card number:
+ * @sample androidx.compose.foundation.samples.CreditCardSample
+ *
  * @param value the input [String] text to be shown in the text field
  * @param onValueChange the callback that is triggered when the input service updates the text. An
  * updated text comes as a parameter of the callback
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
index 67043c8..e660af9 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/interoperability/InteropArchitecture.kt
@@ -29,8 +29,6 @@
 import android.util.AttributeSet
 import android.widget.LinearLayout
 import android.widget.TextView
-import androidx.activity.OnBackPressedCallback
-import androidx.activity.OnBackPressedDispatcher
 import androidx.activity.compose.setContent
 import androidx.appcompat.app.AppCompatActivity
 import androidx.compose.foundation.layout.Column
@@ -38,13 +36,11 @@
 import androidx.compose.material.Text
 import androidx.compose.material.TextField
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.livedata.observeAsState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.ComposeView
 import androidx.lifecycle.LiveData
@@ -126,41 +122,20 @@
 
 private object InteropArchitectureSnippet3 {
     @Composable
-    fun BackHandler(
-        enabled: Boolean,
-        backDispatcher: OnBackPressedDispatcher,
-        onBack: () -> Unit
-    ) {
-
-        // Safely update the current `onBack` lambda when a new one is provided
-        val currentOnBack by rememberUpdatedState(onBack)
-
-        // Remember in Composition a back callback that calls the `onBack` lambda
-        val backCallback = remember {
-            // Always intercept back events. See the SideEffect for a more complete version
-            object : OnBackPressedCallback(true) {
-                override fun handleOnBackPressed() {
-                    currentOnBack()
-                }
-            }
+    fun rememberAnalytics(user: User): FirebaseAnalytics {
+        val analytics: FirebaseAnalytics = remember {
+            // START - DO NOT COPY IN CODE SNIPPET
+            FirebaseAnalytics()
+            // END - DO NOT COPY IN CODE SNIPPET, just use /* ... */
         }
 
-        // On every successful composition, update the callback with the `enabled` value
-        // to tell `backCallback` whether back events should be intercepted or not
+        // On every successful composition, update FirebaseAnalytics with
+        // the userType from the current User, ensuring that future analytics
+        // events have this metadata attached
         SideEffect {
-            backCallback.isEnabled = enabled
+            analytics.setUserProperty("userType", user.userType)
         }
-
-        // If `backDispatcher` changes, dispose and reset the effect
-        DisposableEffect(backDispatcher) {
-            // Add callback to the backDispatcher
-            backDispatcher.addCallback(backCallback)
-
-            // When the effect leaves the Composition, remove the callback
-            onDispose {
-                backCallback.remove()
-            }
-        }
+        return analytics
     }
 }
 
@@ -215,3 +190,8 @@
         return GreetingViewModel(userId) as T
     }
 }
+
+private class User(val userType: String = "user")
+private class FirebaseAnalytics {
+    fun setUserProperty(name: String, value: String) {}
+}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/sideeffects/SideEffects.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/sideeffects/SideEffects.kt
index 98c63c6..d5a5c46 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/sideeffects/SideEffects.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/sideeffects/SideEffects.kt
@@ -21,8 +21,6 @@
 
 package androidx.compose.integration.docs.sideeffects
 
-import androidx.activity.OnBackPressedCallback
-import androidx.activity.OnBackPressedDispatcher
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -49,12 +47,23 @@
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
+import kotlin.Boolean
+import kotlin.Exception
+import kotlin.Long
+import kotlin.Nothing
+import kotlin.String
+import kotlin.Suppress
+import kotlin.Unit
 import kotlin.random.Random
 
 /**
@@ -142,61 +151,56 @@
 
 private object SideEffectsSnippet4 {
     @Composable
-    fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -> Unit) {
+    fun HomeScreen(
+        lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+        onStart: () -> Unit, // Send the 'started' analytics event
+        onStop: () -> Unit // Send the 'stopped' analytics event
+    ) {
+        // Safely update the current lambdas when a new one is provided
+        val currentOnStart by rememberUpdatedState(onStart)
+        val currentOnStop by rememberUpdatedState(onStop)
 
-        // Safely update the current `onBack` lambda when a new one is provided
-        val currentOnBack by rememberUpdatedState(onBack)
-
-        // Remember in Composition a back callback that calls the `onBack` lambda
-        val backCallback = remember {
-            // Always intercept back events. See the SideEffect for a more complete version
-            object : OnBackPressedCallback(true) {
-                override fun handleOnBackPressed() {
-                    currentOnBack()
+        // If `lifecycleOwner` changes, dispose and reset the effect
+        DisposableEffect(lifecycleOwner) {
+            // Create an observer that triggers our remembered callbacks
+            // for sending analytics events
+            val observer = LifecycleEventObserver { _, event ->
+                if (event == Lifecycle.Event.ON_START) {
+                    currentOnStart()
+                } else if (event == Lifecycle.Event.ON_STOP) {
+                    currentOnStop()
                 }
             }
-        }
 
-        // If `backDispatcher` changes, dispose and reset the effect
-        DisposableEffect(backDispatcher) {
-            // Add callback to the backDispatcher
-            backDispatcher.addCallback(backCallback)
+            // Add the observer to the lifecycle
+            lifecycleOwner.lifecycle.addObserver(observer)
 
-            // When the effect leaves the Composition, remove the callback
+            // When the effect leaves the Composition, remove the observer
             onDispose {
-                backCallback.remove()
+                lifecycleOwner.lifecycle.removeObserver(observer)
             }
         }
+
+        /* Home screen content */
     }
 }
 
 private object SideEffectsSnippet5 {
     @Composable
-    fun BackHandler(
-        backDispatcher: OnBackPressedDispatcher,
-        enabled: Boolean = true, // Whether back events should be intercepted or not
-        onBack: () -> Unit
-    ) {
-        // START - DO NOT COPY IN CODE SNIPPET
-        val currentOnBack by rememberUpdatedState(onBack)
-
-        val backCallback = remember {
-            // Always intercept back events. See the SideEffect for a more complete version
-            object : OnBackPressedCallback(true) {
-                override fun handleOnBackPressed() {
-                    currentOnBack()
-                }
-            }
+    fun rememberAnalytics(user: User): FirebaseAnalytics {
+        val analytics: FirebaseAnalytics = remember {
+            // START - DO NOT COPY IN CODE SNIPPET
+            FirebaseAnalytics()
+            // END - DO NOT COPY IN CODE SNIPPET, just use /* ... */
         }
-        // END - DO NOT COPY IN CODE SNIPPET, just use /* ... */
 
-        // On every successful composition, update the callback with the `enabled` value
-        // to tell `backCallback` whether back events should be intercepted or not
+        // On every successful composition, update FirebaseAnalytics with
+        // the userType from the current User, ensuring that future analytics
+        // events have this metadata attached
         SideEffect {
-            backCallback.isEnabled = enabled
+            analytics.setUserProperty("userType", user.userType)
         }
-
-        /* Rest of the code */
+        return analytics
     }
 }
 
@@ -269,24 +273,29 @@
 
 private object SideEffectsSnippet9 {
     @Composable
-    fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -> Unit) {
-        // START - DO NOT COPY IN CODE SNIPPET
-        val currentOnBack by rememberUpdatedState(onBack)
+    fun HomeScreen(
+        lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+        onStart: () -> Unit, // Send the 'started' analytics event
+        onStop: () -> Unit // Send the 'stopped' analytics event
+    ) {
+        // These values never change in Composition
+        val currentOnStart by rememberUpdatedState(onStart)
+        val currentOnStop by rememberUpdatedState(onStop)
 
-        val backCallback = remember {
-            // Always intercept back events. See the SideEffect for a more complete version
-            object : OnBackPressedCallback(true) {
-                override fun handleOnBackPressed() {
-                    currentOnBack()
+        DisposableEffect(lifecycleOwner) {
+            val observer = LifecycleEventObserver { _, event ->
+                // START - DO NOT COPY IN CODE SNIPPET
+                if (event == Lifecycle.Event.ON_START) {
+                    currentOnStart()
+                } else if (event == Lifecycle.Event.ON_STOP) {
+                    currentOnStop()
                 }
+                // END - DO NOT COPY IN CODE SNIPPET, just use /* ... */
             }
-        }
-        // END - DO NOT COPY IN CODE SNIPPET, just use /* ... */
 
-        DisposableEffect(backDispatcher) {
-            backDispatcher.addCallback(backCallback)
+            lifecycleOwner.lifecycle.addObserver(observer)
             onDispose {
-                backCallback.remove()
+                lifecycleOwner.lifecycle.removeObserver(observer)
             }
         }
     }
@@ -315,13 +324,17 @@
     fun load(url: String): Image? = if (Random.nextInt() == 0) Image() else null // Avoid warnings
 }
 
+private class FirebaseAnalytics {
+    fun setUserProperty(name: String, value: String) {}
+}
+
 private sealed class Result<out R> {
     data class Success<out T>(val data: T) : Result<T>()
     object Loading : Result<Nothing>()
     object Error : Result<Nothing>()
 }
 
-private class User
+private class User(val userType: String = "user")
 private class Weather
 private class Greeting(val name: String)
 private fun prepareGreeting(user: User, weather: Weather) = Greeting("haha")
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/state/State.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/state/State.kt
index cac44a1..9e17c64 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/state/State.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/state/State.kt
@@ -19,29 +19,33 @@
 
 package androidx.compose.integration.docs.state
 
+import android.content.res.Resources
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.OutlinedTextField
+import androidx.compose.material.Scaffold
+import androidx.compose.material.ScaffoldState
 import androidx.compose.material.Text
-import androidx.compose.material.TextField
+import androidx.compose.material.rememberScaffoldState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.livedata.observeAsState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.saveable.listSaver
 import androidx.compose.runtime.saveable.mapSaver
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.rememberNavController
+import kotlinx.coroutines.launch
 
 /**
  * This file lets DevRel track changes to snippets present in
@@ -132,46 +136,6 @@
 }
 
 private object StateSnippet5 {
-    class HelloViewModel : ViewModel() {
-
-        // LiveData holds state which is observed by the UI
-        // (state flows down from ViewModel)
-        private val _name = MutableLiveData("")
-        val name: LiveData<String> = _name
-
-        // onNameChanged is an event we're defining that the UI can invoke
-        // (events flow up from UI)
-        fun onNameChanged(newName: String) {
-            _name.value = newName
-        }
-    }
-
-    @Composable
-    fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
-        // by default, viewModel() follows the Lifecycle as the Activity or Fragment
-        // that calls HelloScreen(). This lifecycle can be modified by callers of HelloScreen.
-
-        // name is the _current_ value of [helloViewModel.name]
-        // with an initial value of ""
-        val name: String by helloViewModel.name.observeAsState("")
-
-        Column {
-            Text(text = name)
-            TextField(
-                value = name,
-                onValueChange = { helloViewModel.onNameChanged(it) },
-                label = { Text("Name") }
-            )
-        }
-    }
-}
-
-@Composable
-private fun StateSnippet6() {
-    val nameState: State<String> = helloViewModel.name.observeAsState("")
-}
-
-private object StateSnippet7 {
     @Parcelize
     data class City(val name: String, val country: String) : Parcelable
 
@@ -183,7 +147,7 @@
     }
 }
 
-private object StateSnippet8 {
+private object StateSnippet6 {
     data class City(val name: String, val country: String)
 
     val CitySaver = run {
@@ -204,7 +168,7 @@
 }
 
 @Composable
-private fun StateSnippets9() {
+private fun StateSnippets7() {
     data class City(val name: String, val country: String)
 
     val CitySaver = listSaver<City, Any>(
@@ -220,6 +184,90 @@
     }
 }
 
+@Composable
+private fun StateSnippets8() {
+    @Composable
+    fun MyApp() {
+        MyTheme {
+            val scaffoldState = rememberScaffoldState()
+            val coroutineScope = rememberCoroutineScope()
+
+            Scaffold(scaffoldState = scaffoldState) {
+                MyContent(
+                    showSnackbar = { message ->
+                        coroutineScope.launch {
+                            scaffoldState.snackbarHostState.showSnackbar(message)
+                        }
+                    }
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun StateSnippets9() {
+
+    // Plain class that manages App's UI logic and UI elements' state
+    class MyAppState(
+        val scaffoldState: ScaffoldState,
+        val navController: NavHostController,
+        private val resources: Resources,
+        /* ... */
+    ) {
+        val bottomBarTabs = /* State */
+            // DO NOT COPY IN DAC
+            Unit
+
+        // Logic to decide when to show the bottom bar
+        val shouldShowBottomBar: Boolean
+            @Composable get() = /* ... */
+                // DO NOT COPY IN DAC
+                false
+
+        // Navigation logic, which is a type of UI logic
+        fun navigateToBottomBarRoute(route: String) { /* ... */ }
+
+        // Show snackbar using Resources
+        fun showSnackbar(message: String) { /* ... */ }
+    }
+
+    @Composable
+    fun rememberMyAppState(
+        scaffoldState: ScaffoldState = rememberScaffoldState(),
+        navController: NavHostController = rememberNavController(),
+        resources: Resources = LocalContext.current.resources,
+        /* ... */
+    ) = remember(scaffoldState, navController, resources, /* ... */) {
+        MyAppState(scaffoldState, navController, resources, /* ... */)
+    }
+}
+
+@Composable
+private fun StateSnippets10() {
+    @Composable
+    fun MyApp() {
+        MyTheme {
+            val myAppState = rememberMyAppState()
+            Scaffold(
+                scaffoldState = myAppState.scaffoldState,
+                bottomBar = {
+                    if (myAppState.shouldShowBottomBar) {
+                        BottomBar(
+                            tabs = myAppState.bottomBarTabs,
+                            navigateToRoute = {
+                                myAppState.navigateToBottomBarRoute(it)
+                            }
+                        )
+                    }
+                }
+            ) {
+                NavHost(navController = myAppState.navController, "initial") { /* ... */ }
+            }
+        }
+    }
+}
+
 /*
  * Fakes needed for snippets to build:
  */
@@ -234,25 +282,37 @@
     }
 }
 
-private lateinit var helloViewModel: StateSnippet5.HelloViewModel
+@Composable
+private fun MyTheme(content: @Composable () -> Unit) {}
 
-private class HelloViewModel : ViewModel() {
+@Composable
+private fun MyContent(showSnackbar: (String) -> Unit) {}
 
-    // LiveData holds state which is observed by the UI
-    // (state flows down from ViewModel)
-    private val _name = MutableLiveData("")
-    val name: LiveData<String> = _name
+@Composable
+private fun BottomBar(tabs: Unit, navigateToRoute: (String) -> Unit) {}
 
-    // onNameChanged is an event we're defining that the UI can invoke
-    // (events flow up from UI)
-    fun onNameChanged(newName: String) {
-        _name.value = newName
-    }
+@Composable
+private fun rememberMyAppState(
+    scaffoldState: ScaffoldState = rememberScaffoldState(),
+    navController: NavHostController = rememberNavController(),
+    resources: Resources = LocalContext.current.resources
+) = remember(scaffoldState, navController, resources) {
+    MyAppState(scaffoldState, navController, resources)
+}
+
+private class MyAppState(
+    val scaffoldState: ScaffoldState,
+    val navController: NavHostController,
+    private val resources: Resources,
+) {
+    val shouldShowBottomBar: Boolean = false
+    val bottomBarTabs = Unit
+    fun navigateToBottomBarRoute(route: String) {}
 }
 
 /**
  * Add fake Parcelize and Parcelable to avoid adding AndroidX wide dependency on
  * kotlin-parcelize just for snippets
  */
-annotation class Parcelize
-interface Parcelable
\ No newline at end of file
+private annotation class Parcelize
+private interface Parcelable
diff --git a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
index 1ae18ca..e6ca169 100644
--- a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
+++ b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.Indication
 import androidx.compose.foundation.IndicationInstance
 import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.InteractionSource
@@ -247,7 +248,6 @@
     private var currentInteraction: Interaction? = null
 
     fun handleInteraction(interaction: Interaction, scope: CoroutineScope) {
-        // TODO: handle focus states
         when (interaction) {
             is HoverInteraction.Enter -> {
                 interactions.add(interaction)
@@ -255,6 +255,12 @@
             is HoverInteraction.Exit -> {
                 interactions.remove(interaction.enter)
             }
+            is FocusInteraction.Focus -> {
+                interactions.add(interaction)
+            }
+            is FocusInteraction.Unfocus -> {
+                interactions.remove(interaction.focus)
+            }
             is DragInteraction.Start -> {
                 interactions.add(interaction)
             }
@@ -274,6 +280,7 @@
             if (newInteraction != null) {
                 val targetAlpha = when (interaction) {
                     is HoverInteraction.Enter -> rippleAlpha.value.hoveredAlpha
+                    is FocusInteraction.Focus -> rippleAlpha.value.focusedAlpha
                     is DragInteraction.Start -> rippleAlpha.value.draggedAlpha
                     else -> 0f
                 }
@@ -319,26 +326,24 @@
 /**
  * @return the [AnimationSpec] used when transitioning to [interaction], either from a previous
  * state, or no state.
- *
- * TODO: handle focus states
  */
 private fun incomingStateLayerAnimationSpecFor(interaction: Interaction): AnimationSpec<Float> {
     return when (interaction) {
-        is DragInteraction.Start -> TweenSpec(durationMillis = 45, easing = LinearEasing)
         is HoverInteraction.Enter -> DefaultTweenSpec
+        is FocusInteraction.Focus -> TweenSpec(durationMillis = 45, easing = LinearEasing)
+        is DragInteraction.Start -> TweenSpec(durationMillis = 45, easing = LinearEasing)
         else -> DefaultTweenSpec
     }
 }
 
 /**
  * @return the [AnimationSpec] used when transitioning away from [interaction], to no state.
- *
- * TODO: handle focus states
  */
 private fun outgoingStateLayerAnimationSpecFor(interaction: Interaction?): AnimationSpec<Float> {
     return when (interaction) {
-        is DragInteraction.Start -> TweenSpec(durationMillis = 150, easing = LinearEasing)
         is HoverInteraction.Enter -> DefaultTweenSpec
+        is FocusInteraction.Focus -> DefaultTweenSpec
+        is DragInteraction.Start -> TweenSpec(durationMillis = 150, easing = LinearEasing)
         else -> DefaultTweenSpec
     }
 }
diff --git a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/RippleTheme.kt b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/RippleTheme.kt
index 09118d6..3197721 100644
--- a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/RippleTheme.kt
+++ b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/RippleTheme.kt
@@ -104,7 +104,7 @@
  * RippleAlpha defines the alpha of the ripple / state layer for different [Interaction]s.
  *
  * @property draggedAlpha the alpha used when the ripple is dragged
- * @property focusedAlpha not currently supported
+ * @property focusedAlpha the alpha used when the ripple is focused
  * @property hoveredAlpha the alpha used when the ripple is hovered
  * @property pressedAlpha the alpha used when the ripple is pressed
  */
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index bc66cd7..0ca2bd3 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -640,3 +640,10 @@
 
 }
 
+package androidx.compose.material.internal {
+
+  public final class ExposedDropdownMenuPopupKt {
+  }
+
+}
+
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index 1e5c805..ee7b665 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -373,10 +373,8 @@
 
   @androidx.compose.material.ExperimentalMaterialApi public final class ExposedDropdownMenuDefaults {
     method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public void TrailingIcon(boolean expanded, optional kotlin.jvm.functions.Function0<kotlin.Unit> onIconClick);
-    method public androidx.compose.ui.window.PopupProperties getPopupProperties();
     method @androidx.compose.runtime.Composable public androidx.compose.material.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long backgroundColor, optional long cursorColor, optional long errorCursorColor, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long leadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long trailingIconColor, optional long focusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long backgroundColor, optional long cursorColor, optional long errorCursorColor, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long leadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long trailingIconColor, optional long focusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
-    property public final androidx.compose.ui.window.PopupProperties PopupProperties;
     field public static final androidx.compose.material.ExposedDropdownMenuDefaults INSTANCE;
   }
 
@@ -849,3 +847,10 @@
 
 }
 
+package androidx.compose.material.internal {
+
+  public final class ExposedDropdownMenuPopupKt {
+  }
+
+}
+
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index bc66cd7..0ca2bd3 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -640,3 +640,10 @@
 
 }
 
+package androidx.compose.material.internal {
+
+  public final class ExposedDropdownMenuPopupKt {
+  }
+
+}
+
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 38af064..5651411 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -47,6 +47,11 @@
         implementation(project(":compose:foundation:foundation-layout"))
         implementation("androidx.compose.ui:ui-util:1.0.0")
 
+        // TODO: remove next 3 dependencies when b/202810604 is fixed
+        implementation("androidx.savedstate:savedstate:1.1.0")
+        implementation("androidx.lifecycle:lifecycle-runtime:2.3.0")
+        implementation("androidx.lifecycle:lifecycle-viewmodel:2.3.0")
+
         testImplementation(libs.testRules)
         testImplementation(libs.testRunner)
         testImplementation(libs.junit)
@@ -97,6 +102,11 @@
 
             androidMain.dependencies {
                 api("androidx.annotation:annotation:1.1.0")
+
+                // TODO: remove next 3 dependencies when b/202810604 is fixed
+                implementation("androidx.savedstate:savedstate:1.1.0")
+                implementation("androidx.lifecycle:lifecycle-runtime:2.3.0")
+                implementation("androidx.lifecycle:lifecycle-viewmodel:2.3.0")
             }
 
             desktopMain.dependencies {
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/DropdownMenuVariationDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/DropdownMenuVariationDemo.kt
deleted file mode 100644
index e97d59a..0000000
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/DropdownMenuVariationDemo.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright 2020 The Android expanded Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material.demos
-
-import android.view.WindowManager
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.requiredHeight
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.Button
-import androidx.compose.material.DropdownMenu
-import androidx.compose.material.DropdownMenuItem
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.window.PopupProperties
-
-@OptIn(ExperimentalComposeUiApi::class)
-@Composable
-fun DropdownMenuVariationsDemo() {
-    Column(
-        modifier = Modifier.fillMaxHeight().width(300.dp),
-        horizontalAlignment = Alignment.CenterHorizontally
-    ) {
-        Spacer(Modifier.requiredHeight(10.dp))
-
-        Text("Default Menu (not focusable)", fontSize = 20.sp)
-        DropdownMenuInstance()
-
-        Spacer(Modifier.requiredHeight(10.dp))
-
-        Text("Focusable menu", fontSize = 20.sp)
-        DropdownMenuInstance(PopupProperties(focusable = true))
-
-        Spacer(Modifier.requiredHeight(10.dp))
-
-        Text(
-            "Focusable Menu which propagates clicks",
-            fontSize = 20.sp
-        )
-        DropdownMenuInstance(
-            PopupProperties(
-                focusable = true,
-                updateAndroidWindowManagerFlags = { flags ->
-                    flags or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                }
-            )
-        )
-    }
-}
-
-@Composable
-fun DropdownMenuInstance(popupProperties: PopupProperties = PopupProperties()) {
-    val options = listOf(
-        "Refresh",
-        "Settings",
-        "Send Feedback",
-        "Help",
-        "Signout"
-    )
-
-    var expanded by remember { mutableStateOf(false) }
-
-    Box {
-        Button(onClick = { expanded = true }) {
-            Text("SHOW MENU")
-        }
-        DropdownMenu(
-            expanded = expanded,
-            onDismissRequest = { expanded = false },
-            properties = popupProperties,
-        ) {
-            options.forEach {
-                DropdownMenuItem(onClick = { expanded = false }) {
-                    Text(it)
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
index c69e980..087aa05 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
@@ -35,7 +35,6 @@
 val MaterialDemos = DemoCategory(
     "Material",
     listOf(
-        ComposableDemo("ExposedDropdownMenu") { ExposedDropdownMenuDemo() },
         DemoCategory(
             "AlertDialog",
             listOf(
@@ -76,7 +75,7 @@
             "Menus",
             listOf(
                 ComposableDemo("Dropdown Menu positioning") { MenuDemo() },
-                ComposableDemo("Menu variations") { DropdownMenuVariationsDemo() },
+                ComposableDemo("ExposedDropdownMenu") { ExposedDropdownMenuDemo() }
             )
         ),
         ComposableDemo("Navigation Rail") { NavigationRailDemo() },
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonScreenshotTest.kt
index 58a3991..c3f82d1 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ButtonScreenshotTest.kt
@@ -21,6 +21,9 @@
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.hasClickAction
@@ -121,4 +124,32 @@
             .captureToImage()
             .assertAgainstGolden(screenshotRule, "button_hover")
     }
+
+    @Test
+    fun focus() {
+        val focusRequester = FocusRequester()
+
+        rule.setMaterialContent {
+            Box(Modifier.requiredSize(200.dp, 100.dp).wrapContentSize()) {
+                Button(
+                    onClick = { },
+                    modifier = Modifier
+                        // Normally this is only focusable in non-touch mode, so let's force it to
+                        // always be focusable so we can test how it appears
+                        .focusProperties { canFocus = true }
+                        .focusRequester(focusRequester)
+                ) { }
+            }
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        rule.waitForIdle()
+
+        rule.onRoot()
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "button_focus")
+    }
 }
\ No newline at end of file
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxScreenshotTest.kt
index 0d8bbba..b133192 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/CheckboxScreenshotTest.kt
@@ -22,6 +22,9 @@
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.state.ToggleableState
 import androidx.compose.ui.test.ExperimentalTestApi
@@ -226,6 +229,33 @@
         assertToggeableAgainstGolden("checkbox_hover")
     }
 
+    @Test
+    fun checkBoxTest_focus() {
+        val focusRequester = FocusRequester()
+
+        rule.setMaterialContent {
+            Box(wrap.testTag(wrapperTestTag)) {
+                Checkbox(
+                    modifier = wrap
+                        // Normally this is only focusable in non-touch mode, so let's force it to
+                        // always be focusable so we can test how it appears
+                        .focusProperties { canFocus = true }
+                        .focusRequester(focusRequester),
+                    checked = true,
+                    onCheckedChange = { }
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        rule.waitForIdle()
+
+        assertToggeableAgainstGolden("checkbox_focus")
+    }
+
     private fun assertToggeableAgainstGolden(goldenName: String) {
         // TODO: replace with find(isToggeable()) after b/157687898 is fixed
         rule.onNodeWithTag(wrapperTestTag)
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
index 08cac4f..f8efa44 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
@@ -23,6 +23,9 @@
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.hasClickAction
@@ -142,4 +145,34 @@
             .captureToImage()
             .assertAgainstGolden(screenshotRule, "fab_hover")
     }
+
+    @Test
+    fun focus() {
+        val focusRequester = FocusRequester()
+
+        rule.setMaterialContent {
+            Box(Modifier.requiredSize(100.dp, 100.dp).wrapContentSize()) {
+                FloatingActionButton(
+                    onClick = { },
+                    modifier = Modifier
+                        // Normally this is only focusable in non-touch mode, so let's force it to
+                        // always be focusable so we can test how it appears
+                        .focusProperties { canFocus = true }
+                        .focusRequester(focusRequester)
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = null)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        rule.waitForIdle()
+
+        rule.onRoot()
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "fab_focus")
+    }
 }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MaterialRippleThemeTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MaterialRippleThemeTest.kt
index d1509f7..50e5533 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MaterialRippleThemeTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MaterialRippleThemeTest.kt
@@ -26,6 +26,7 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -129,6 +130,28 @@
     }
 
     @Test
+    fun bounded_lightTheme_highLuminance_focused() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.White
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = true,
+            lightTheme = true,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            FocusInteraction.Focus(),
+            "ripple_bounded_light_highluminance_focused",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.24f)
+        )
+    }
+
+    @Test
     fun bounded_lightTheme_highLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -195,6 +218,28 @@
     }
 
     @Test
+    fun bounded_lightTheme_lowLuminance_focused() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.Black
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = true,
+            lightTheme = true,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            FocusInteraction.Focus(),
+            "ripple_bounded_light_lowluminance_focused",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.12f)
+        )
+    }
+
+    @Test
     fun bounded_lightTheme_lowLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -261,6 +306,28 @@
     }
 
     @Test
+    fun bounded_darkTheme_highLuminance_focused() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.White
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = true,
+            lightTheme = false,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            FocusInteraction.Focus(),
+            "ripple_bounded_dark_highluminance_focused",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.12f)
+        )
+    }
+
+    @Test
     fun bounded_darkTheme_highLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -329,6 +396,29 @@
     }
 
     @Test
+    fun bounded_darkTheme_lowLuminance_focused() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.Black
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = true,
+            lightTheme = false,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            FocusInteraction.Focus(),
+            "ripple_bounded_dark_lowluminance_focused",
+            // Low luminance content in dark theme should use a white ripple by default
+            calculateResultingRippleColor(Color.White, rippleOpacity = 0.12f)
+        )
+    }
+
+    @Test
     fun bounded_darkTheme_lowLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -396,6 +486,28 @@
     }
 
     @Test
+    fun unbounded_lightTheme_highLuminance_focused() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.White
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = false,
+            lightTheme = true,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            FocusInteraction.Focus(),
+            "ripple_unbounded_light_highluminance_focused",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.24f)
+        )
+    }
+
+    @Test
     fun unbounded_lightTheme_highLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -462,6 +574,28 @@
     }
 
     @Test
+    fun unbounded_lightTheme_lowLuminance_focused() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.Black
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = false,
+            lightTheme = true,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            FocusInteraction.Focus(),
+            "ripple_unbounded_light_lowluminance_focused",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.12f)
+        )
+    }
+
+    @Test
     fun unbounded_lightTheme_lowLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -528,6 +662,28 @@
     }
 
     @Test
+    fun unbounded_darkTheme_highLuminance_focused() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.White
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = false,
+            lightTheme = false,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            FocusInteraction.Focus(),
+            "ripple_unbounded_dark_highluminance_focused",
+            calculateResultingRippleColor(contentColor, rippleOpacity = 0.12f)
+        )
+    }
+
+    @Test
     fun unbounded_darkTheme_highLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -596,6 +752,29 @@
     }
 
     @Test
+    fun unbounded_darkTheme_lowLuminance_focused() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.Black
+
+        val scope = rule.setRippleContent(
+            interactionSource = interactionSource,
+            bounded = false,
+            lightTheme = false,
+            contentColor = contentColor
+        )
+
+        assertRippleMatches(
+            scope,
+            interactionSource,
+            FocusInteraction.Focus(),
+            "ripple_unbounded_dark_lowluminance_focused",
+            // Low luminance content in dark theme should use a white ripple by default
+            calculateResultingRippleColor(Color.White, rippleOpacity = 0.12f)
+        )
+    }
+
+    @Test
     fun unbounded_darkTheme_lowLuminance_dragged() {
         val interactionSource = MutableInteractionSource()
 
@@ -721,6 +900,57 @@
     }
 
     @Test
+    fun customRippleTheme_focused() {
+        val interactionSource = MutableInteractionSource()
+
+        val contentColor = Color.Black
+
+        val rippleColor = Color.Red
+        val expectedAlpha = 0.5f
+        val rippleAlpha = RippleAlpha(expectedAlpha, expectedAlpha, expectedAlpha, expectedAlpha)
+
+        val rippleTheme = object : RippleTheme {
+            @Composable
+            override fun defaultColor() = rippleColor
+
+            @Composable
+            override fun rippleAlpha() = rippleAlpha
+        }
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            MaterialTheme {
+                CompositionLocalProvider(LocalRippleTheme provides rippleTheme) {
+                    Surface(contentColor = contentColor) {
+                        Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+                            RippleBoxWithBackground(
+                                interactionSource,
+                                rememberRipple(),
+                                bounded = true
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        val expectedColor = calculateResultingRippleColor(
+            rippleColor,
+            rippleOpacity = expectedAlpha
+        )
+
+        assertRippleMatches(
+            scope!!,
+            interactionSource,
+            FocusInteraction.Focus(),
+            "ripple_customtheme_focused",
+            expectedColor
+        )
+    }
+
+    @Test
     fun customRippleTheme_dragged() {
         val interactionSource = MutableInteractionSource()
 
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt
index 99fe9e3..c34f5cc 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt
@@ -24,6 +24,9 @@
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.captureToImage
@@ -114,6 +117,31 @@
     }
 
     @Test
+    fun radioButtonTest_focused() {
+        val focusRequester = FocusRequester()
+
+        rule.setMaterialContent {
+            Box(wrap.testTag(wrapperTestTag)) {
+                RadioButton(
+                    selected = false,
+                    onClick = {},
+                    modifier = Modifier
+                        // Normally this is only focusable in non-touch mode, so let's force it to
+                        // always be focusable so we can test how it appears
+                        .focusProperties { canFocus = true }
+                        .focusRequester(focusRequester)
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        assertSelectableAgainstGolden("radioButton_focused")
+    }
+
+    @Test
     fun radioButtonTest_disabled_selected() {
         rule.setMaterialContent {
             Box(wrap.testTag(wrapperTestTag)) {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
index ad24ccf..59ce852 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt
@@ -26,6 +26,9 @@
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
@@ -255,6 +258,33 @@
         assertToggeableAgainstGolden("switch_hover")
     }
 
+    @Test
+    fun switchTest_focus() {
+        val focusRequester = FocusRequester()
+
+        rule.setMaterialContent {
+            Box(wrapperModifier) {
+                Switch(
+                    checked = true,
+                    onCheckedChange = { },
+                    modifier = Modifier
+                        // Normally this is only focusable in non-touch mode, so let's force it to
+                        // always be focusable so we can test how it appears
+                        .focusProperties { canFocus = true }
+                        .focusRequester(focusRequester)
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        rule.waitForIdle()
+
+        assertToggeableAgainstGolden("switch_focus")
+    }
+
     private fun assertToggeableAgainstGolden(goldenName: String) {
         rule.onNodeWithTag(wrapperTestTag)
             .captureToImage()
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
index 8a1e35f..35f5b24 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
@@ -52,7 +52,6 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.screenshot.AndroidXScreenshotTestRule
@@ -465,7 +464,6 @@
         assertAgainstGolden("outlinedTextField_readOnly_focused")
     }
 
-    @FlakyTest(bugId = 178510985)
     @Test
     fun outlinedTextField_readOnly_scrolled() {
         rule.setMaterialContent {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt
index 836981b..5909fc2 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt
@@ -51,7 +51,6 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.screenshot.AndroidXScreenshotTestRule
@@ -453,7 +452,6 @@
         assertAgainstGolden("textField_readOnly_focused")
     }
 
-    @FlakyTest(bugId = 178510985)
     @Test
     fun textField_readOnly_scrolled() {
         rule.setContent {
diff --git a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt
index 3b85ffd..f81d554a 100644
--- a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt
+++ b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt
@@ -18,8 +18,8 @@
 import android.graphics.Rect
 import android.view.View
 import android.view.ViewTreeObserver
-import android.view.WindowManager
 import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.foundation.interaction.InteractionSource
@@ -30,6 +30,7 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material.internal.ExposedDropdownMenuPopup
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.Immutable
@@ -40,13 +41,12 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.rotate
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.changedToUp
@@ -61,9 +61,8 @@
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.onClick
 import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.util.fastAll
-import androidx.compose.ui.window.PopupProperties
 import kotlinx.coroutines.coroutineScope
 import kotlin.math.max
 
@@ -196,42 +195,50 @@
         onDismissRequest: () -> Unit,
         modifier: Modifier = Modifier,
         content: @Composable ColumnScope.() -> Unit
-    ) = DropdownMenu(
-        expanded = expanded,
-        onDismissRequest = onDismissRequest,
-        modifier = modifier.exposedDropdownSize(),
-        properties = ExposedDropdownMenuDefaults.PopupProperties,
-        content = content
-    )
+    ) {
+        // TODO(b/202810604): use DropdownMenu when PopupProperties constructor is stable
+        // return DropdownMenu(
+        //     expanded = expanded,
+        //     onDismissRequest = onDismissRequest,
+        //     modifier = modifier.exposedDropdownSize(),
+        //     properties = ExposedDropdownMenuDefaults.PopupProperties,
+        //     content = content
+        // )
+
+        val expandedStates = remember { MutableTransitionState(false) }
+        expandedStates.targetState = expanded
+
+        if (expandedStates.currentState || expandedStates.targetState) {
+            val transformOriginState = remember { mutableStateOf(TransformOrigin.Center) }
+            val density = LocalDensity.current
+            val popupPositionProvider = DropdownMenuPositionProvider(
+                DpOffset.Zero,
+                density
+            ) { parentBounds, menuBounds ->
+                transformOriginState.value = calculateTransformOrigin(parentBounds, menuBounds)
+            }
+
+            ExposedDropdownMenuPopup(
+                onDismissRequest = onDismissRequest,
+                popupPositionProvider = popupPositionProvider
+            ) {
+                DropdownMenuContent(
+                    expandedStates = expandedStates,
+                    transformOriginState = transformOriginState,
+                    modifier = modifier.exposedDropdownSize(),
+                    content = content
+                )
+            }
+        }
+    }
 }
 
 /**
  * Contains default values used by Exposed Dropdown Menu.
  */
-@OptIn(ExperimentalComposeUiApi::class)
 @ExperimentalMaterialApi
 object ExposedDropdownMenuDefaults {
     /**
-     * The default Popup Properties for ExposedDropdownMenu inside ExposedDropdownMenuBox.
-     * ExposedDropdownMenu will be focusable, will propagate clicks, work with input method,
-     * won't be dismissed when Text Field was clicked.
-     */
-    val PopupProperties = PopupProperties(
-        focusable = true,
-        dismissOnOutsideClick = { offset: Offset?, bounds: IntRect ->
-            if (offset == null) false
-            else {
-                offset.x < bounds.left || offset.x > bounds.right ||
-                    offset.y < bounds.top || offset.y > bounds.bottom
-            }
-        },
-        updateAndroidWindowManagerFlags = { flags ->
-            flags or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager
-                .LayoutParams.FLAG_ALT_FOCUSABLE_IM
-        }
-    )
-
-    /**
      * Default trailing icon for Exposed Dropdown Menu.
      *
      * @param expanded Whether [ExposedDropdownMenuBoxScope.ExposedDropdownMenu]
diff --git a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.kt b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.kt
new file mode 100644
index 0000000..62c1c68
--- /dev/null
+++ b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/internal/ExposedDropdownMenuPopup.kt
@@ -0,0 +1,469 @@
+/*
+ * 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.material.internal
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.view.Gravity
+import android.view.KeyEvent
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.view.ViewTreeObserver
+import android.view.WindowManager
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionContext
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCompositionContext
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.R
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.AbstractComposeView
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.ViewRootForInspector
+import androidx.compose.ui.semantics.popup
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastMap
+import androidx.compose.ui.window.PopupPositionProvider
+import androidx.lifecycle.ViewTreeLifecycleOwner
+import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import androidx.savedstate.ViewTreeSavedStateRegistryOwner
+import java.util.UUID
+import kotlin.math.roundToInt
+
+/**
+ * Popup specific for exposed dropdown menus. b/202810604. Should not be used in other components.
+ */
+@Composable
+internal fun ExposedDropdownMenuPopup(
+    onDismissRequest: (() -> Unit)? = null,
+    popupPositionProvider: PopupPositionProvider,
+    content: @Composable () -> Unit
+) {
+    val view = LocalView.current
+    val density = LocalDensity.current
+    val testTag = LocalPopupTestTag.current
+    val layoutDirection = LocalLayoutDirection.current
+    val parentComposition = rememberCompositionContext()
+    val currentContent by rememberUpdatedState(content)
+    val popupId = rememberSaveable { UUID.randomUUID() }
+    val popupLayout = remember {
+        PopupLayout(
+            onDismissRequest = onDismissRequest,
+            testTag = testTag,
+            composeView = view,
+            density = density,
+            initialPositionProvider = popupPositionProvider,
+            popupId = popupId
+        ).apply {
+            setContent(parentComposition) {
+                SimpleStack(
+                    Modifier
+                        .semantics { this.popup() }
+                        // Get the size of the content
+                        .onSizeChanged {
+                            popupContentSize = it
+                            updatePosition()
+                        }
+                        // Hide the popup while we can't position it correctly
+                        .alpha(if (canCalculatePosition) 1f else 0f)
+                ) {
+                    currentContent()
+                }
+            }
+        }
+    }
+
+    DisposableEffect(popupLayout) {
+        popupLayout.show()
+        popupLayout.updateParameters(
+            onDismissRequest = onDismissRequest,
+            testTag = testTag,
+            layoutDirection = layoutDirection
+        )
+        onDispose {
+            popupLayout.disposeComposition()
+            // Remove the window
+            popupLayout.dismiss()
+        }
+    }
+
+    SideEffect {
+        popupLayout.updateParameters(
+            onDismissRequest = onDismissRequest,
+            testTag = testTag,
+            layoutDirection = layoutDirection
+        )
+    }
+
+    DisposableEffect(popupPositionProvider) {
+        popupLayout.positionProvider = popupPositionProvider
+        popupLayout.updatePosition()
+        onDispose {}
+    }
+
+    // TODO(soboleva): Look at module arrangement so that Box can be
+    //  used instead of this custom Layout
+    // Get the parent's position, size and layout direction
+    Layout(
+        content = {},
+        modifier = Modifier.onGloballyPositioned { childCoordinates ->
+            val coordinates = childCoordinates.parentLayoutCoordinates!!
+            val layoutSize = coordinates.size
+
+            val position = coordinates.positionInWindow()
+            val layoutPosition = IntOffset(position.x.roundToInt(), position.y.roundToInt())
+
+            popupLayout.parentBounds = IntRect(layoutPosition, layoutSize)
+            // Update the popup's position
+            popupLayout.updatePosition()
+        }
+    ) { _, _ ->
+        popupLayout.parentLayoutDirection = layoutDirection
+        layout(0, 0) {}
+    }
+}
+
+// TODO(b/142431825): This is a hack to work around Popups not using Semantics for test tags
+//  We should either remove it, or come up with an abstracted general solution that isn't specific
+//  to Popup
+internal val LocalPopupTestTag = compositionLocalOf { "DEFAULT_TEST_TAG" }
+
+// TODO(soboleva): Look at module dependencies so that we can get code reuse between
+// Popup's SimpleStack and Box.
+@Suppress("NOTHING_TO_INLINE")
+@Composable
+private inline fun SimpleStack(modifier: Modifier, noinline content: @Composable () -> Unit) {
+    Layout(content = content, modifier = modifier) { measurables, constraints ->
+        when (measurables.size) {
+            0 -> layout(0, 0) {}
+            1 -> {
+                val p = measurables[0].measure(constraints)
+                layout(p.width, p.height) {
+                    p.placeRelative(0, 0)
+                }
+            }
+            else -> {
+                val placeables = measurables.fastMap { it.measure(constraints) }
+                var width = 0
+                var height = 0
+                for (i in 0..placeables.lastIndex) {
+                    val p = placeables[i]
+                    width = maxOf(width, p.width)
+                    height = maxOf(height, p.height)
+                }
+                layout(width, height) {
+                    for (i in 0..placeables.lastIndex) {
+                        val p = placeables[i]
+                        p.placeRelative(0, 0)
+                    }
+                }
+            }
+        }
+    }
+}
+
+/**
+ * The layout the popup uses to display its content.
+ *
+ * @param composeView The parent view of the popup which is the AndroidComposeView.
+ */
+@SuppressLint("ViewConstructor")
+private class PopupLayout(
+    private var onDismissRequest: (() -> Unit)?,
+    var testTag: String,
+    private val composeView: View,
+    density: Density,
+    initialPositionProvider: PopupPositionProvider,
+    popupId: UUID
+) : AbstractComposeView(composeView.context),
+    ViewRootForInspector,
+    ViewTreeObserver.OnGlobalLayoutListener {
+    private val windowManager =
+        composeView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+    private val params = createLayoutParams()
+
+    /** The logic of positioning the popup relative to its parent. */
+    var positionProvider = initialPositionProvider
+
+    // Position params
+    var parentLayoutDirection: LayoutDirection = LayoutDirection.Ltr
+    var parentBounds: IntRect? by mutableStateOf(null)
+    var popupContentSize: IntSize? by mutableStateOf(null)
+
+    // Track parent bounds and content size; only show popup once we have both
+    val canCalculatePosition by derivedStateOf { parentBounds != null && popupContentSize != null }
+
+    private val maxSupportedElevation = 30.dp
+
+    // The window visible frame used for the last popup position calculation.
+    private val previousWindowVisibleFrame = Rect()
+    private val tmpWindowVisibleFrame = Rect()
+
+    override val subCompositionView: AbstractComposeView get() = this
+
+    // Specific to exposed dropdown menus.
+    private val dismissOnOutsideClick = { offset: Offset?, bounds: IntRect ->
+        if (offset == null) false
+        else {
+            offset.x < bounds.left || offset.x > bounds.right ||
+                offset.y < bounds.top || offset.y > bounds.bottom
+        }
+    }
+
+    init {
+        id = android.R.id.content
+        ViewTreeLifecycleOwner.set(this, ViewTreeLifecycleOwner.get(composeView))
+        ViewTreeViewModelStoreOwner.set(this, ViewTreeViewModelStoreOwner.get(composeView))
+        ViewTreeSavedStateRegistryOwner.set(this, ViewTreeSavedStateRegistryOwner.get(composeView))
+        composeView.viewTreeObserver.addOnGlobalLayoutListener(this)
+        // Set unique id for AbstractComposeView. This allows state restoration for the state
+        // defined inside the Popup via rememberSaveable()
+        setTag(R.id.compose_view_saveable_id_tag, "Popup:$popupId")
+
+        // Enable children to draw their shadow by not clipping them
+        clipChildren = false
+        // Allocate space for elevation
+        with(density) { elevation = maxSupportedElevation.toPx() }
+        // Simple outline to force window manager to allocate space for shadow.
+        // Note that the outline affects clickable area for the dismiss listener. In case of shapes
+        // like circle the area for dismiss might be to small (rectangular outline consuming clicks
+        // outside of the circle).
+        outlineProvider = object : ViewOutlineProvider() {
+            override fun getOutline(view: View, result: Outline) {
+                result.setRect(0, 0, view.width, view.height)
+                // We set alpha to 0 to hide the view's shadow and let the composable to draw its
+                // own shadow. This still enables us to get the extra space needed in the surface.
+                result.alpha = 0f
+            }
+        }
+    }
+
+    private var content: @Composable () -> Unit by mutableStateOf({})
+
+    override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
+        private set
+
+    fun show() {
+        windowManager.addView(this, params)
+    }
+
+    fun setContent(parent: CompositionContext, content: @Composable () -> Unit) {
+        setParentCompositionContext(parent)
+        this.content = content
+        shouldCreateCompositionOnAttachedToWindow = true
+    }
+
+    @Composable
+    override fun Content() {
+        content()
+    }
+
+    /**
+     * Taken from PopupWindow
+     */
+    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (event.keyCode == KeyEvent.KEYCODE_BACK) {
+            if (keyDispatcherState == null) {
+                return super.dispatchKeyEvent(event)
+            }
+            if (event.action == KeyEvent.ACTION_DOWN && event.repeatCount == 0) {
+                val state = keyDispatcherState
+                state?.startTracking(event, this)
+                return true
+            } else if (event.action == KeyEvent.ACTION_UP) {
+                val state = keyDispatcherState
+                if (state != null && state.isTracking(event) && !event.isCanceled) {
+                    onDismissRequest?.invoke()
+                    return true
+                }
+            }
+        }
+        return super.dispatchKeyEvent(event)
+    }
+
+    fun updateParameters(
+        onDismissRequest: (() -> Unit)?,
+        testTag: String,
+        layoutDirection: LayoutDirection
+    ) {
+        this.onDismissRequest = onDismissRequest
+        this.testTag = testTag
+        superSetLayoutDirection(layoutDirection)
+    }
+
+    /**
+     * Updates the position of the popup based on current position properties.
+     */
+    fun updatePosition() {
+        val parentBounds = parentBounds ?: return
+        val popupContentSize = popupContentSize ?: return
+
+        val windowSize = previousWindowVisibleFrame.let {
+            composeView.getWindowVisibleDisplayFrame(it)
+            val bounds = it.toIntBounds()
+            IntSize(width = bounds.width, height = bounds.height)
+        }
+
+        val popupPosition = positionProvider.calculatePosition(
+            parentBounds,
+            windowSize,
+            parentLayoutDirection,
+            popupContentSize
+        )
+
+        params.x = popupPosition.x
+        params.y = popupPosition.y
+
+        windowManager.updateViewLayout(this, params)
+    }
+
+    /**
+     * Remove the view from the [WindowManager].
+     */
+    fun dismiss() {
+        ViewTreeLifecycleOwner.set(this, null)
+        composeView.viewTreeObserver.removeOnGlobalLayoutListener(this)
+        windowManager.removeViewImmediate(this)
+    }
+
+    /**
+     * Handles touch screen motion events and calls [onDismissRequest] when the
+     * users clicks outside the popup.
+     */
+    override fun onTouchEvent(event: MotionEvent?): Boolean {
+        event ?: return super.onTouchEvent(event)
+
+        // Note that this implementation is taken from PopupWindow. It actually does not seem to
+        // matter whether we return true or false as some upper layer decides on whether the
+        // event is propagated to other windows or not. So for focusable the event is consumed but
+        // for not focusable it is propagated to other windows.
+        if (
+            (
+                (event.action == MotionEvent.ACTION_DOWN) &&
+                    (
+                        (event.x < 0) ||
+                            (event.x >= width) ||
+                            (event.y < 0) ||
+                            (event.y >= height)
+                        )
+                ) ||
+            event.action == MotionEvent.ACTION_OUTSIDE
+        ) {
+            val parentBounds = parentBounds
+            val shouldDismiss = parentBounds == null || dismissOnOutsideClick(
+                if (event.x != 0f || event.y != 0f) {
+                    Offset(
+                        params.x + event.x,
+                        params.y + event.y
+                    )
+                } else null,
+                parentBounds
+            )
+            if (shouldDismiss) {
+                onDismissRequest?.invoke()
+                return true
+            }
+        }
+        return super.onTouchEvent(event)
+    }
+
+    override fun setLayoutDirection(layoutDirection: Int) {
+        // Do nothing. ViewRootImpl will call this method attempting to set the layout direction
+        // from the context's locale, but we have one already from the parent composition.
+    }
+
+    // Sets the "real" layout direction for our content that we obtain from the parent composition.
+    private fun superSetLayoutDirection(layoutDirection: LayoutDirection) {
+        val direction = when (layoutDirection) {
+            LayoutDirection.Ltr -> android.util.LayoutDirection.LTR
+            LayoutDirection.Rtl -> android.util.LayoutDirection.RTL
+        }
+        super.setLayoutDirection(direction)
+    }
+
+    /**
+     * Initialize the LayoutParams specific to [android.widget.PopupWindow].
+     */
+    private fun createLayoutParams(): WindowManager.LayoutParams {
+        return WindowManager.LayoutParams().apply {
+            // Start to position the popup in the top left corner, a new position will be calculated
+            gravity = Gravity.START or Gravity.TOP
+
+            // Flags specific to exposed dropdown menu.
+            flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+            softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
+
+            type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
+
+            // Get the Window token from the parent view
+            token = composeView.applicationWindowToken
+
+            // Wrap the frame layout which contains composable content
+            width = WindowManager.LayoutParams.WRAP_CONTENT
+            height = WindowManager.LayoutParams.WRAP_CONTENT
+
+            format = PixelFormat.TRANSLUCENT
+
+            // accessibilityTitle is not exposed as a public API therefore we set popup window
+            // title which is used as a fallback by a11y services
+            title = composeView.context.resources.getString(R.string.default_popup_window_title)
+        }
+    }
+
+    private fun Rect.toIntBounds() = IntRect(
+        left = left,
+        top = top,
+        right = right,
+        bottom = bottom
+    )
+
+    override fun onGlobalLayout() {
+        // Update the position of the popup, in case getWindowVisibleDisplayFrame has changed.
+        composeView.getWindowVisibleDisplayFrame(tmpWindowVisibleFrame)
+        if (tmpWindowVisibleFrame != previousWindowVisibleFrame) {
+            updatePosition()
+        }
+    }
+}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
index 47e85a9..c866582 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt
@@ -19,6 +19,7 @@
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.VectorConverter
 import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.InteractionSource
@@ -328,7 +329,6 @@
      */
     val IconSpacing = 8.dp
 
-    // TODO: b/152525426 add support for focused states
     /**
      * Creates a [ButtonElevation] that will animate between the provided values according to the
      * Material specification for a [Button].
@@ -363,7 +363,7 @@
      * is pressed.
      * @param disabledElevation the elevation to use when the [Button] is not enabled.
      * @param hoveredElevation the elevation to use when the [Button] is enabled and is hovered.
-     * @param focusedElevation not currently supported.
+     * @param focusedElevation the elevation to use when the [Button] is enabled and is focused.
      */
     @Suppress("UNUSED_PARAMETER")
     @Composable
@@ -374,12 +374,19 @@
         hoveredElevation: Dp = 4.dp,
         focusedElevation: Dp = 4.dp,
     ): ButtonElevation {
-        return remember(defaultElevation, pressedElevation, disabledElevation, hoveredElevation) {
+        return remember(
+            defaultElevation,
+            pressedElevation,
+            disabledElevation,
+            hoveredElevation,
+            focusedElevation
+        ) {
             DefaultButtonElevation(
                 defaultElevation = defaultElevation,
                 pressedElevation = pressedElevation,
                 disabledElevation = disabledElevation,
                 hoveredElevation = hoveredElevation,
+                focusedElevation = focusedElevation
             )
         }
     }
@@ -491,6 +498,7 @@
     private val pressedElevation: Dp,
     private val disabledElevation: Dp,
     private val hoveredElevation: Dp,
+    private val focusedElevation: Dp,
 ) : ButtonElevation {
     @Composable
     override fun elevation(enabled: Boolean, interactionSource: InteractionSource): State<Dp> {
@@ -504,6 +512,12 @@
                     is HoverInteraction.Exit -> {
                         interactions.remove(interaction.enter)
                     }
+                    is FocusInteraction.Focus -> {
+                        interactions.add(interaction)
+                    }
+                    is FocusInteraction.Unfocus -> {
+                        interactions.remove(interaction.focus)
+                    }
                     is PressInteraction.Press -> {
                         interactions.add(interaction)
                     }
@@ -525,6 +539,7 @@
             when (interaction) {
                 is PressInteraction.Press -> pressedElevation
                 is HoverInteraction.Enter -> hoveredElevation
+                is FocusInteraction.Focus -> focusedElevation
                 else -> defaultElevation
             }
         }
@@ -541,6 +556,7 @@
                 val lastInteraction = when (animatable.targetValue) {
                     pressedElevation -> PressInteraction.Press(Offset.Zero)
                     hoveredElevation -> HoverInteraction.Enter()
+                    focusedElevation -> FocusInteraction.Focus()
                     else -> null
                 }
                 animatable.animateElevation(
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
index 56e3198..bb9c2f4 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Elevation.kt
@@ -22,6 +22,7 @@
 import androidx.compose.animation.core.FastOutSlowInEasing
 import androidx.compose.animation.core.TweenSpec
 import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.PressInteraction
@@ -81,6 +82,7 @@
             is PressInteraction.Press -> DefaultIncomingSpec
             is DragInteraction.Start -> DefaultIncomingSpec
             is HoverInteraction.Enter -> DefaultIncomingSpec
+            is FocusInteraction.Focus -> DefaultIncomingSpec
             else -> null
         }
     }
@@ -96,6 +98,7 @@
             is PressInteraction.Press -> DefaultOutgoingSpec
             is DragInteraction.Start -> DefaultOutgoingSpec
             is HoverInteraction.Enter -> HoveredOutgoingSpec
+            is FocusInteraction.Focus -> DefaultOutgoingSpec
             else -> null
         }
     }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
index ef23afe..59d7247 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.VectorConverter
+import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.InteractionSource
@@ -206,7 +207,6 @@
  * Contains the default values used by [FloatingActionButton]
  */
 object FloatingActionButtonDefaults {
-    // TODO: b/152525426 add support for focused states
     /**
      * Creates a [FloatingActionButtonElevation] that will animate between the provided values
      * according to the Material specification.
@@ -238,7 +238,8 @@
      * pressed.
      * @param hoveredElevation the elevation to use when the [FloatingActionButton] is
      * hovered.
-     * @param focusedElevation not currently supported.
+     * @param focusedElevation the elevation to use when the [FloatingActionButton] is
+     * focused.
      */
     @Suppress("UNUSED_PARAMETER")
     @Composable
@@ -248,11 +249,12 @@
         hoveredElevation: Dp = 8.dp,
         focusedElevation: Dp = 8.dp,
     ): FloatingActionButtonElevation {
-        return remember(defaultElevation, pressedElevation, hoveredElevation) {
+        return remember(defaultElevation, pressedElevation, hoveredElevation, focusedElevation) {
             DefaultFloatingActionButtonElevation(
                 defaultElevation = defaultElevation,
                 pressedElevation = pressedElevation,
                 hoveredElevation = hoveredElevation,
+                focusedElevation = focusedElevation
             )
         }
     }
@@ -265,7 +267,8 @@
 private class DefaultFloatingActionButtonElevation(
     private val defaultElevation: Dp,
     private val pressedElevation: Dp,
-    private val hoveredElevation: Dp
+    private val hoveredElevation: Dp,
+    private val focusedElevation: Dp
 ) : FloatingActionButtonElevation {
     @Composable
     override fun elevation(interactionSource: InteractionSource): State<Dp> {
@@ -279,6 +282,12 @@
                     is HoverInteraction.Exit -> {
                         interactions.remove(interaction.enter)
                     }
+                    is FocusInteraction.Focus -> {
+                        interactions.add(interaction)
+                    }
+                    is FocusInteraction.Unfocus -> {
+                        interactions.remove(interaction.focus)
+                    }
                     is PressInteraction.Press -> {
                         interactions.add(interaction)
                     }
@@ -297,6 +306,7 @@
         val target = when (interaction) {
             is PressInteraction.Press -> pressedElevation
             is HoverInteraction.Enter -> hoveredElevation
+            is FocusInteraction.Focus -> focusedElevation
             else -> defaultElevation
         }
 
@@ -306,6 +316,7 @@
             val lastInteraction = when (animatable.targetValue) {
                 pressedElevation -> PressInteraction.Press(Offset.Zero)
                 hoveredElevation -> HoverInteraction.Enter()
+                focusedElevation -> FocusInteraction.Focus()
                 else -> null
             }
             animatable.animateElevation(
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt
index 136f02e..4a38486 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ProgressIndicator.kt
@@ -28,7 +28,6 @@
 import androidx.compose.animation.core.rememberInfiniteTransition
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.progressSemantics
 import androidx.compose.material.ProgressIndicatorDefaults.IndicatorBackgroundOpacity
@@ -80,7 +79,6 @@
         modifier
             .progressSemantics(progress)
             .size(LinearIndicatorWidth, LinearIndicatorHeight)
-            .focusable()
     ) {
         val strokeWidth = size.height
         drawLinearIndicatorBackground(backgroundColor, strokeWidth)
@@ -157,7 +155,6 @@
         modifier
             .progressSemantics()
             .size(LinearIndicatorWidth, LinearIndicatorHeight)
-            .focusable()
     ) {
         val strokeWidth = size.height
         drawLinearIndicatorBackground(backgroundColor, strokeWidth)
@@ -237,7 +234,6 @@
         modifier
             .progressSemantics(progress)
             .size(CircularIndicatorDiameter)
-            .focusable()
     ) {
         // Start at 12 O'clock
         val startAngle = 270f
@@ -318,7 +314,6 @@
         modifier
             .progressSemantics()
             .size(CircularIndicatorDiameter)
-            .focusable()
     ) {
 
         val currentRotationAngleOffset = (currentRotation * RotationAngleOffset) % 360f
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 bfec8ff..d7ad38c 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
@@ -758,7 +758,7 @@
     steps: Int = 0
 ): Modifier {
     val coerced = value.coerceIn(valueRange.start, valueRange.endInclusive)
-    return semantics(mergeDescendants = true) {
+    return semantics {
         if (!enabled) disabled()
         setProgress(
             action = { targetValue ->
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index ad1b9a4..17ac330 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1,6 +1,13 @@
 // Signature format: 4.0
 package androidx.compose.material3 {
 
+  public final class AlertDialogKt {
+  }
+
+  public final class AndroidAlertDialog_androidKt {
+    method @androidx.compose.runtime.Composable public static void AlertDialog(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function0<kotlin.Unit> confirmButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional long iconContentColor, optional long titleContentColor, optional long textContentColor, optional androidx.compose.ui.window.DialogProperties properties);
+  }
+
   public final class AppBarKt {
     method @androidx.compose.runtime.Composable public static void SmallCenteredTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index b246af7..a39ea89 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -1,6 +1,13 @@
 // Signature format: 4.0
 package androidx.compose.material3 {
 
+  public final class AlertDialogKt {
+  }
+
+  public final class AndroidAlertDialog_androidKt {
+    method @androidx.compose.runtime.Composable public static void AlertDialog(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function0<kotlin.Unit> confirmButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional long iconContentColor, optional long titleContentColor, optional long textContentColor, optional androidx.compose.ui.window.DialogProperties properties);
+  }
+
   public final class AppBarKt {
     method @androidx.compose.runtime.Composable public static void SmallCenteredTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index ad1b9a4..17ac330 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1,6 +1,13 @@
 // Signature format: 4.0
 package androidx.compose.material3 {
 
+  public final class AlertDialogKt {
+  }
+
+  public final class AndroidAlertDialog_androidKt {
+    method @androidx.compose.runtime.Composable public static void AlertDialog(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function0<kotlin.Unit> confirmButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional long iconContentColor, optional long titleContentColor, optional long textContentColor, optional androidx.compose.ui.window.DialogProperties properties);
+  }
+
   public final class AppBarKt {
     method @androidx.compose.runtime.Composable public static void SmallCenteredTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
index abc7cdf..b9754bf 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
@@ -57,7 +57,20 @@
     examples = TopAppBarExamples
 )
 
+private val Dialog = Component(
+    id = 3,
+    name = "Dialog",
+    description = "Material 3 basic dialogs",
+    // No dialog icon
+    tintIcon = true,
+    guidelinesUrl = "", // No  guidelines yet
+    docsUrl = "", // No docs yet
+    sourceUrl = "$Material3SourceUrl/AlertDialog.kt",
+    examples = DialogExamples
+)
+
 val Components = listOf(
     Color,
+    Dialog,
     TopAppBar
 )
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index 0457449..5d12d40 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -19,6 +19,8 @@
 package androidx.compose.material3.catalog.library.model
 
 import androidx.compose.material3.catalog.library.util.SampleSourceUrl
+import androidx.compose.material3.samples.AlertDialogSample
+import androidx.compose.material3.samples.AlertDialogWithIconSample
 import androidx.compose.material3.samples.ColorSchemeSample
 import androidx.compose.material3.samples.EnterAlwaysSmallTopAppBar
 import androidx.compose.material3.samples.PinnedSmallTopAppBar
@@ -44,6 +46,22 @@
         ) { ColorSchemeSample() },
     )
 
+private const val DialogExampleDescription = "Basic Dialog examples"
+private const val DialogExampleSourceUrl = "$SampleSourceUrl/AlertDialogSamples.kt"
+val DialogExamples =
+    listOf(
+        Example(
+            name = ::AlertDialogSample.name,
+            description = DialogExampleDescription,
+            sourceUrl = DialogExampleSourceUrl,
+        ) { AlertDialogSample() },
+        Example(
+            name = ::AlertDialogWithIconSample.name,
+            description = DialogExampleDescription,
+            sourceUrl = DialogExampleSourceUrl,
+        ) { AlertDialogWithIconSample() },
+    )
+
 private const val TopAppBarExampleDescription = "Top app bar examples"
 private const val TopAppBarExampleSourceUrl = "$SampleSourceUrl/AppBarSamples.kt"
 val TopAppBarExamples =
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/theme/Theme.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/theme/Theme.kt
index 178262e..271002a 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/theme/Theme.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/ui/theme/Theme.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.ColorScheme
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.catalog.library.model.ColorMode
 import androidx.compose.material3.catalog.library.model.TextDirection
@@ -29,6 +30,7 @@
 import androidx.compose.material3.lightColorScheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
@@ -36,37 +38,31 @@
 import androidx.compose.ui.unit.LayoutDirection
 
 @Composable
-@Suppress("UNUSED_PARAMETER")
 fun CatalogTheme(
     theme: Theme,
     content: @Composable () -> Unit
 ) {
-    // TODO(b/201804011): Add sampleDynamicColorScheme
     val colorScheme =
         if (theme.colorMode == ColorMode.TrueDynamic &&
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             val context = LocalContext.current
-            when (theme.themeMode) {
-                ThemeMode.Light -> dynamicLightColorScheme(context)
-                ThemeMode.Dark -> dynamicDarkColorScheme(context)
-                ThemeMode.System ->
-                    if (!isSystemInDarkTheme()) {
-                        dynamicLightColorScheme(context)
-                    } else {
-                        dynamicDarkColorScheme(context)
-                    }
-            }
+            colorSchemeFromThemeMode(
+                themeMode = theme.themeMode,
+                lightColorScheme = dynamicLightColorScheme(context),
+                darkColorScheme = dynamicDarkColorScheme(context),
+            )
+        } else if (theme.colorMode == ColorMode.SampleDynamic) {
+            colorSchemeFromThemeMode(
+                themeMode = theme.themeMode,
+                lightColorScheme = DynamicLightColorSchemeSample,
+                darkColorScheme = DynamicDarkColorSchemeSample,
+            )
         } else {
-            when (theme.themeMode) {
-                ThemeMode.Light -> lightColorScheme()
-                ThemeMode.Dark -> darkColorScheme()
-                ThemeMode.System ->
-                    if (!isSystemInDarkTheme()) {
-                        lightColorScheme()
-                    } else {
-                        darkColorScheme()
-                    }
-            }
+            colorSchemeFromThemeMode(
+                themeMode = theme.themeMode,
+                lightColorScheme = lightColorScheme(),
+                darkColorScheme = darkColorScheme(),
+            )
         }
 
     val layoutDirection = when (theme.textDirection) {
@@ -89,3 +85,78 @@
         )
     }
 }
+
+@Composable
+fun colorSchemeFromThemeMode(
+    themeMode: ThemeMode,
+    lightColorScheme: ColorScheme,
+    darkColorScheme: ColorScheme
+): ColorScheme {
+    return when (themeMode) {
+        ThemeMode.Light -> lightColorScheme
+        ThemeMode.Dark -> darkColorScheme
+        ThemeMode.System -> if (!isSystemInDarkTheme()) {
+            lightColorScheme
+        } else {
+            darkColorScheme
+        }
+    }
+}
+
+val DynamicLightColorSchemeSample = ColorScheme(
+    primary = Color(0xFF984816),
+    onPrimary = Color(0xFFFFFFFF),
+    primaryContainer = Color(0xFFFFDBC9),
+    onPrimaryContainer = Color(0xFF341000),
+    inversePrimary = Color(0xFFFFB68F),
+    secondary = Color(0xFF765849),
+    onSecondary = Color(0xFFFFFFFF),
+    secondaryContainer = Color(0xFFFFDBC9),
+    onSecondaryContainer = Color(0xFF2B160B),
+    tertiary = Color(0xFF656032),
+    onTertiary = Color(0xFFFFFFFF),
+    tertiaryContainer = Color(0xFFEBE4AA),
+    onTertiaryContainer = Color(0xFF1F1C00),
+    background = Color(0xFFFCFCFC),
+    onBackground = Color(0xFF201A17),
+    surface = Color(0xFFFCFCFC),
+    onSurface = Color(0xFF201A17),
+    surfaceVariant = Color(0xFFF4DED5),
+    onSurfaceVariant = Color(0xFF53443D),
+    inverseSurface = Color(0xFF362F2C),
+    inverseOnSurface = Color(0xFFFBEEE9),
+    error = Color(0xFFBA1B1B),
+    onError = Color(0xFFFFFFFF),
+    errorContainer = Color(0xFFFFDAD4),
+    onErrorContainer = Color(0xFF410001),
+    outline = Color(0xFF85736B),
+)
+
+val DynamicDarkColorSchemeSample = ColorScheme(
+    primary = Color(0xFFFFB68F),
+    onPrimary = Color(0xFF562000),
+    primaryContainer = Color(0xFF793100),
+    onPrimaryContainer = Color(0xFFFFDBC9),
+    inversePrimary = Color(0xFF984816),
+    secondary = Color(0xFFE6BEAC),
+    onSecondary = Color(0xFF432B1E),
+    secondaryContainer = Color(0xFF5C4032),
+    onSecondaryContainer = Color(0xFFFFDBC9),
+    tertiary = Color(0xFFCFC890),
+    onTertiary = Color(0xFF353107),
+    tertiaryContainer = Color(0xFF4C481C),
+    onTertiaryContainer = Color(0xFFEBE4AA),
+    background = Color(0xFF201A17),
+    onBackground = Color(0xFFEDE0DB),
+    surface = Color(0xFF201A17),
+    onSurface = Color(0xFFEDE0DB),
+    surfaceVariant = Color(0xFF53443D),
+    onSurfaceVariant = Color(0xFFD7C2B9),
+    inverseSurface = Color(0xFFEDE0DB),
+    inverseOnSurface = Color(0xFF362F2C),
+    error = Color(0xFFFFB4A9),
+    onError = Color(0xFF680003),
+    errorContainer = Color(0xFF930006),
+    onErrorContainer = Color(0xFFFFDAD4),
+    outline = Color(0xFFA08D85),
+)
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AlertDialogSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AlertDialogSamples.kt
new file mode 100644
index 0000000..23a3208
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AlertDialogSamples.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.material3.samples
+
+// TODO(b/198216553): Update to use Material 3 TextButton.
+import androidx.annotation.Sampled
+import androidx.compose.material.TextButton
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+
+@Sampled
+@Composable
+fun AlertDialogSample() {
+    val openDialog = remember { mutableStateOf(true) }
+
+    if (openDialog.value) {
+        AlertDialog(
+            onDismissRequest = {
+                // Dismiss the dialog when the user clicks outside the dialog or on the back
+                // button. If you want to disable that functionality, simply use an empty
+                // onDismissRequest.
+                openDialog.value = false
+            },
+            title = {
+                Text(text = "Title")
+            },
+            text = {
+                Text(text = "Turned on by default")
+            },
+            confirmButton = {
+                TextButton(
+                    onClick = {
+                        openDialog.value = false
+                    }
+                ) {
+                    Text("Confirm")
+                }
+            },
+            dismissButton = {
+                TextButton(
+                    onClick = {
+                        openDialog.value = false
+                    }
+                ) {
+                    Text("Dismiss")
+                }
+            }
+        )
+    }
+}
+
+@Sampled
+@Composable
+fun AlertDialogWithIconSample() {
+    val openDialog = remember { mutableStateOf(true) }
+
+    if (openDialog.value) {
+        AlertDialog(
+            onDismissRequest = {
+                // Dismiss the dialog when the user clicks outside the dialog or on the back
+                // button. If you want to disable that functionality, simply use an empty
+                // onDismissRequest.
+                openDialog.value = false
+            },
+            icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
+            title = {
+                Text(text = "Title")
+            },
+            text = {
+                Text(
+                    "This area typically contains the supportive text " +
+                        "which presents the details regarding the Dialog's purpose."
+                )
+            },
+            confirmButton = {
+                TextButton(
+                    onClick = {
+                        openDialog.value = false
+                    }
+                ) {
+                    Text("Confirm")
+                }
+            },
+            dismissButton = {
+                TextButton(
+                    onClick = {
+                        openDialog.value = false
+                    }
+                ) {
+                    Text("Dismiss")
+                }
+            }
+        )
+    }
+}
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AlertDialogScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AlertDialogScreenshotTest.kt
new file mode 100644
index 0000000..b6bd598
--- /dev/null
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AlertDialogScreenshotTest.kt
@@ -0,0 +1,183 @@
+/*
+ * 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.material3
+
+import android.os.Build
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.isDialog
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalTestApi::class)
+class AlertDialogScreenshotTest {
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3)
+
+    @Test
+    fun alertDialog_lightTheme() {
+        composeTestRule.setContent {
+            MaterialTheme {
+                AlertDialog(
+                    onDismissRequest = {},
+                    title = {
+                        Text(text = "Title")
+                    },
+                    text = {
+                        Text(
+                            "This area typically contains the supportive text " +
+                                "which presents the details regarding the Dialog's purpose."
+                        )
+                    },
+                    // TODO(b/198216553): Wrap with Material 3 TextButton when available.
+                    confirmButton = {
+                        Text("Confirm", Modifier.padding(TextButtonContentPadding))
+                    },
+                    dismissButton = {
+                        Text("Dismiss", Modifier.padding(TextButtonContentPadding))
+                    }
+                )
+            }
+        }
+
+        assertAppBarAgainstGolden(goldenIdentifier = "alertDialog_lightTheme")
+    }
+
+    @Test
+    fun alertDialog_darkTheme() {
+        composeTestRule.setContent {
+            MaterialTheme(colorScheme = darkColorScheme()) {
+                AlertDialog(
+                    onDismissRequest = {},
+                    title = {
+                        Text(text = "Title")
+                    },
+                    text = {
+                        Text(
+                            "This area typically contains the supportive text " +
+                                "which presents the details regarding the Dialog's purpose."
+                        )
+                    },
+                    // TODO(b/198216553): Wrap with Material 3 TextButton when available.
+                    confirmButton = {
+                        Text("Confirm", Modifier.padding(TextButtonContentPadding))
+                    },
+                    dismissButton = {
+                        Text("Dismiss", Modifier.padding(TextButtonContentPadding))
+                    }
+                )
+            }
+        }
+
+        assertAppBarAgainstGolden(goldenIdentifier = "alertDialog_darkTheme")
+    }
+
+    @Test
+    fun alertDialog_withIcon_lightTheme() {
+        composeTestRule.setContent {
+            MaterialTheme {
+                AlertDialog(
+                    onDismissRequest = {},
+                    icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
+                    title = {
+                        Text(text = "Title")
+                    },
+                    text = {
+                        Text(
+                            "This area typically contains the supportive text " +
+                                "which presents the details regarding the Dialog's purpose."
+                        )
+                    },
+                    // TODO(b/198216553): Wrap with Material 3 TextButton when available.
+                    confirmButton = {
+                        Text("Confirm", Modifier.padding(TextButtonContentPadding))
+                    },
+                    dismissButton = {
+                        Text("Dismiss", Modifier.padding(TextButtonContentPadding))
+                    }
+                )
+            }
+        }
+
+        assertAppBarAgainstGolden(goldenIdentifier = "alertDialog_withIcon_lightTheme")
+    }
+
+    @Test
+    fun alertDialog_withIcon_darkTheme() {
+        composeTestRule.setContent {
+            MaterialTheme(colorScheme = darkColorScheme()) {
+                AlertDialog(
+                    onDismissRequest = {},
+                    icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
+                    title = {
+                        Text(text = "Title")
+                    },
+                    text = {
+                        Text(
+                            "This area typically contains the supportive text " +
+                                "which presents the details regarding the Dialog's purpose."
+                        )
+                    },
+                    // TODO(b/198216553): Wrap with Material 3 TextButton when available.
+                    confirmButton = {
+                        Text("Confirm", Modifier.padding(TextButtonContentPadding))
+                    },
+                    dismissButton = {
+                        Text("Dismiss", Modifier.padding(TextButtonContentPadding))
+                    }
+                )
+            }
+        }
+
+        assertAppBarAgainstGolden(goldenIdentifier = "alertDialog_withIcon_darkTheme")
+    }
+
+    private fun assertAppBarAgainstGolden(goldenIdentifier: String) {
+        composeTestRule.onNode(isDialog())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, goldenIdentifier)
+    }
+
+    // TODO(b/198216553): Remove once Material 3 TextButton is available.
+    private val TextButtonContentPadding =
+        PaddingValues(
+            start = 8.dp,
+            top = 16.dp,
+            end = 8.dp,
+            bottom = 16.dp,
+        )
+}
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AlertDialogTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AlertDialogTest.kt
new file mode 100644
index 0000000..dcbe719
--- /dev/null
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AlertDialogTest.kt
@@ -0,0 +1,324 @@
+/*
+ * 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.material3
+
+import android.os.Build
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.isDialog
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+class AlertDialogTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun customStyleProperties_shouldApply() {
+        var buttonContentColor = Color.Unspecified
+        var expectedButtonContentColor = Color.Unspecified
+        var iconContentColor = Color.Unspecified
+        var titleContentColor = Color.Unspecified
+        var textContentColor = Color.Unspecified
+        rule.setContent {
+            AlertDialog(
+                onDismissRequest = {},
+                modifier = Modifier.border(10.dp, Color.Blue),
+                icon = {
+                    Icon(Icons.Filled.Favorite, contentDescription = null)
+                    iconContentColor = LocalContentColor.current
+                },
+                title = {
+                    Text(text = "Title")
+                    titleContentColor = LocalContentColor.current
+                },
+                text = {
+                    Text("Text")
+                    textContentColor = LocalContentColor.current
+                },
+                // TODO(b/198216553): Wrap with Material 3 TextButton when available.
+                confirmButton = {
+                    Text("Confirm")
+                    buttonContentColor = LocalContentColor.current
+                    expectedButtonContentColor =
+                        MaterialTheme.colorScheme.fromToken(
+                            androidx.compose.material3.tokens.Dialog.ActionLabelTextColor
+                        )
+                },
+                containerColor = Color.Yellow,
+                tonalElevation = 0.dp,
+                iconContentColor = Color.Green,
+                titleContentColor = Color.Magenta,
+                textContentColor = Color.DarkGray
+            )
+        }
+
+        // Assert background
+        rule.onNode(isDialog())
+            .captureToImage()
+            .assertContainsColor(Color.Yellow) // Background
+            .assertContainsColor(Color.Blue) // Modifier border
+
+        // Assert content colors
+        rule.runOnIdle {
+            assertThat(buttonContentColor).isEqualTo(expectedButtonContentColor)
+            assertThat(iconContentColor).isEqualTo(Color.Green)
+            assertThat(titleContentColor).isEqualTo(Color.Magenta)
+            assertThat(textContentColor).isEqualTo(Color.DarkGray)
+        }
+    }
+
+    /**
+     * Ensure that Dialogs don't press up against the edges of the screen.
+     */
+    @Test
+    fun alertDialogDoesNotConsumeFullScreenWidth() {
+        val dialogWidthCh = Channel<Int>(Channel.CONFLATED)
+        var maxDialogWidth = 0
+        var screenWidth by mutableStateOf(0)
+        rule.setContent {
+            val context = LocalContext.current
+            val density = LocalDensity.current
+            val resScreenWidth = context.resources.configuration.screenWidthDp
+            with(density) {
+                screenWidth = resScreenWidth.dp.roundToPx()
+                maxDialogWidth = AlertDialogMaxWidth.roundToPx()
+            }
+
+            AlertDialog(
+                modifier = Modifier.onSizeChanged { dialogWidthCh.trySend(it.width) }
+                    .fillMaxWidth(),
+                onDismissRequest = {},
+                title = { Text(text = "Title") },
+                text = {
+                    Text(
+                        "This area typically contains the supportive text " +
+                            "which presents the details regarding the Dialog's purpose."
+                    )
+                },
+                // TODO(b/198216553): Wrap with Material 3 TextButton when available.
+                confirmButton = { Text("Confirm") },
+                dismissButton = { Text("Dismiss") },
+            )
+        }
+
+        runBlocking {
+            val dialogWidth = withTimeout(5_000) { dialogWidthCh.receive() }
+            assertThat(dialogWidth).isLessThan(maxDialogWidth)
+            assertThat(dialogWidth).isLessThan(screenWidth)
+        }
+    }
+
+    /** Ensure the Dialog's min width. */
+    @Test
+    fun alertDialog_minWidth() {
+        val dialogWidthCh = Channel<Int>(Channel.CONFLATED)
+        var minDialogWidth = 0
+        rule.setContent {
+            with(LocalDensity.current) { minDialogWidth = AlertDialogMinWidth.roundToPx() }
+            AlertDialog(
+                modifier = Modifier.onSizeChanged { dialogWidthCh.trySend(it.width) },
+                onDismissRequest = {},
+                title = { Text(text = "Title") },
+                text = { Text("Short") },
+                // TODO(b/198216553): Wrap with Material 3 TextButton when available.
+                confirmButton = { Text("Confirm") }
+            )
+        }
+
+        runBlocking {
+            val dialogWidth = withTimeout(5_000) { dialogWidthCh.receive() }
+            assertThat(dialogWidth).isEqualTo(minDialogWidth)
+        }
+    }
+
+    @Test
+    fun alertDialog_withIcon_positioning() {
+        rule.setMaterialContent {
+            AlertDialog(
+                onDismissRequest = {},
+                icon = {
+                    Icon(
+                        Icons.Filled.Favorite,
+                        contentDescription = null,
+                        modifier = Modifier.testTag(IconTestTag)
+                    )
+                },
+                title = { Text(text = "Title", modifier = Modifier.testTag(TitleTestTag)) },
+                text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
+                // TODO(b/198216553): Using IconButton to ensure a minimum touch target of 48dp.
+                //  Wrap with Material 3 TextButton when available.
+                confirmButton = {
+                    IconButton(
+                        onClick = { /* doSomething() */ },
+                        Modifier.testTag(ConfirmButtonTestTag).semantics(mergeDescendants = true) {}
+                    ) {
+                        Icon(Icons.Filled.Check, contentDescription = null)
+                    }
+                },
+                dismissButton = {
+                    IconButton(
+                        onClick = { /* doSomething() */ },
+                        Modifier.testTag(DismissButtonTestTag).semantics(mergeDescendants = true) {}
+                    ) {
+                        Icon(Icons.Filled.Close, contentDescription = null)
+                    }
+                }
+            )
+        }
+
+        val dialogBounds = rule.onNode(isDialog()).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(IconTestTag).getUnclippedBoundsInRoot()
+        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
+        val textBounds = rule.onNodeWithTag(TextTestTag).getUnclippedBoundsInRoot()
+        val confirmBtBounds = rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot()
+        val dismissBtBounds = rule.onNodeWithTag(DismissButtonTestTag).getUnclippedBoundsInRoot()
+
+        rule.onNodeWithTag(IconTestTag)
+            // Dialog's icon should be centered (icon size is 24dp)
+            .assertLeftPositionInRootIsEqualTo((dialogBounds.width - 24.dp) / 2)
+            // Dialog's icon should be 24dp from the top
+            .assertTopPositionInRootIsEqualTo(24.dp)
+
+        rule.onNodeWithTag(TitleTestTag)
+            // Title should be centered (default alignment when an icon presence)
+            .assertLeftPositionInRootIsEqualTo((dialogBounds.width - titleBounds.width) / 2)
+            // Title should be 16dp below the icon.
+            .assertTopPositionInRootIsEqualTo(iconBounds.bottom + 16.dp)
+
+        rule.onNodeWithTag(TextTestTag)
+            // Text should be 24dp from the start.
+            .assertLeftPositionInRootIsEqualTo(24.dp)
+            // Text should be 16dp below the title.
+            .assertTopPositionInRootIsEqualTo(titleBounds.bottom + 16.dp)
+
+        rule.onNodeWithTag(ConfirmButtonTestTag)
+            // Confirm button should be 24dp from the right.
+            .assertLeftPositionInRootIsEqualTo(dialogBounds.right - 24.dp - confirmBtBounds.width)
+            // Buttons should be 18dp from the bottom (test button default height is 48dp).
+            .assertTopPositionInRootIsEqualTo(dialogBounds.bottom - 18.dp - 48.dp)
+
+        // Check the measurements between the components.
+        (confirmBtBounds.top - textBounds.bottom).assertIsEqualTo(
+            18.dp,
+            "padding between the text and the button"
+        )
+        (confirmBtBounds.top).assertIsEqualTo(dismissBtBounds.top, "dialog buttons top alignment")
+        (confirmBtBounds.bottom).assertIsEqualTo(
+            dismissBtBounds.bottom,
+            "dialog buttons bottom alignment"
+        )
+        (confirmBtBounds.left - 8.dp).assertIsEqualTo(
+            dismissBtBounds.right,
+            "horizontal padding between the dialog buttons"
+        )
+    }
+
+    @Test
+    fun alertDialog_positioning() {
+        rule.setMaterialContent {
+            AlertDialog(
+                onDismissRequest = {},
+                title = { Text(text = "Title", modifier = Modifier.testTag(TitleTestTag)) },
+                text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
+                // TODO(b/198216553): Using IconButton to ensure a minimum touch target of 48dp.
+                //  Wrap with Material 3 TextButton when available.
+                confirmButton = {},
+                dismissButton = {
+                    IconButton(
+                        onClick = { /* doSomething() */ },
+                        Modifier.testTag(DismissButtonTestTag).semantics(mergeDescendants = true) {}
+                    ) {
+                        Icon(Icons.Filled.Close, contentDescription = null)
+                    }
+                }
+            )
+        }
+
+        val dialogBounds = rule.onNode(isDialog()).getUnclippedBoundsInRoot()
+        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
+        val textBounds = rule.onNodeWithTag(TextTestTag).getUnclippedBoundsInRoot()
+        val dismissBtBounds = rule.onNodeWithTag(DismissButtonTestTag).getUnclippedBoundsInRoot()
+
+        rule.onNodeWithTag(TitleTestTag)
+            // Title should 24dp from the left.
+            .assertLeftPositionInRootIsEqualTo(24.dp)
+            // Title should be 24dp from the top.
+            .assertTopPositionInRootIsEqualTo(24.dp)
+
+        rule.onNodeWithTag(TextTestTag)
+            // Text should be 24dp from the start.
+            .assertLeftPositionInRootIsEqualTo(24.dp)
+            // Text should be 16dp below the title.
+            .assertTopPositionInRootIsEqualTo(titleBounds.bottom + 16.dp)
+
+        rule.onNodeWithTag(DismissButtonTestTag)
+            // Dismiss button should be 24dp from the right.
+            .assertLeftPositionInRootIsEqualTo(dialogBounds.right - 24.dp - dismissBtBounds.width)
+            // Buttons should be 18dp from the bottom (test button default height is 48dp).
+            .assertTopPositionInRootIsEqualTo(dialogBounds.bottom - 18.dp - 48.dp)
+
+        (dismissBtBounds.top - textBounds.bottom).assertIsEqualTo(
+            18.dp,
+            "padding between the text and the button"
+        )
+    }
+}
+
+private val AlertDialogMinWidth = 280.dp
+private val AlertDialogMaxWidth = 560.dp
+private const val IconTestTag = "icon"
+private const val TitleTestTag = "title"
+private const val TextTestTag = "text"
+private const val ConfirmButtonTestTag = "confirmButton"
+private const val DismissButtonTestTag = "dismissButton"
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidAlertDialog.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidAlertDialog.android.kt
new file mode 100644
index 0000000..e80bf5a
--- /dev/null
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidAlertDialog.android.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.material3
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+
+// TODO(b/197880751): Update the docs with the 3P location of the AlertDialogSamples, specs, and
+//  images.
+/**
+ * Material Design basic dialog.
+ *
+ * Dialogs interrupt users with urgent information, details, or actions.
+ *
+ * The dialog will position its buttons, typically [TextButton]s, based on the available space.
+ * By default it will try to place them horizontally next to each other and fallback to horizontal
+ * placement if not enough space is available.
+ *
+ * Simple usage:
+ * @sample androidx.compose.material3.samples.AlertDialogSample
+ *
+ * Usage with a "Hero" icon:
+ * @sample androidx.compose.material3.samples.AlertDialogWithIconSample
+ *
+ * @param onDismissRequest Executes when the user tries to dismiss the Dialog by clicking outside
+ * or pressing the back button. This is not called when the dismiss button is clicked.
+ * @param confirmButton A button which is meant to confirm a proposed action, thus resolving
+ * what triggered the dialog. The dialog does not set up any events for this button so they need
+ * to be set up by the caller.
+ * @param modifier Modifier to be applied to the layout of the dialog.
+ * @param dismissButton A button which is meant to dismiss the dialog. The dialog does not set up
+ * any events for this button so they need to be set up by the caller.
+ * @param icon An optional icon that will appear above the [title] or above the [text], in case a
+ * title was not provided.
+ * @param title The title of the Dialog which should specify the purpose of the Dialog. The title
+ * is not mandatory, because there may be sufficient information inside the [text].
+ * @param text The text which presents the details regarding the Dialog's purpose.
+ * @param shape Defines the Dialog's shape
+ * @param containerColor The container color of the dialog.
+ * @param tonalElevation When [containerColor] is [ColorScheme.surface], a higher tonal elevation
+ * value will result in a darker dialog color in light theme and lighter color in dark theme.
+ * See also [Surface].
+ * @param iconContentColor The content color used for the icon.
+ * @param titleContentColor The content color used for the title.
+ * @param textContentColor The content color used for the text.
+ * @param properties Typically platform specific properties to further configure the dialog.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AlertDialog(
+    onDismissRequest: () -> Unit,
+    confirmButton: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    dismissButton: @Composable (() -> Unit)? = null,
+    icon: @Composable (() -> Unit)? = null,
+    title: @Composable (() -> Unit)? = null,
+    text: @Composable (() -> Unit)? = null,
+    shape: Shape = androidx.compose.material3.tokens.Dialog.ContainerShape,
+    containerColor: Color =
+        MaterialTheme.colorScheme.fromToken(
+            androidx.compose.material3.tokens.Dialog.ContainerColor
+        ),
+    tonalElevation: Dp = androidx.compose.material3.tokens.Dialog.ContainerElevation,
+    iconContentColor: Color = MaterialTheme.colorScheme.fromToken(
+        androidx.compose.material3.tokens.Dialog.WithIconIconColor
+    ),
+    titleContentColor: Color = MaterialTheme.colorScheme.fromToken(
+        androidx.compose.material3.tokens.Dialog.SubheadColor
+    ),
+    textContentColor: Color = MaterialTheme.colorScheme.fromToken(
+        androidx.compose.material3.tokens.Dialog.SupportingTextColor
+    ),
+    properties: DialogProperties = DialogProperties()
+) {
+    Dialog(
+        onDismissRequest = onDismissRequest,
+        properties = properties
+    ) {
+        AlertDialogContent(
+            buttons = {
+                AlertDialogFlowRow(
+                    mainAxisSpacing = ButtonsMainAxisSpacing,
+                    crossAxisSpacing = ButtonsCrossAxisSpacing
+                ) {
+                    dismissButton?.invoke()
+                    confirmButton()
+                }
+            },
+            modifier = modifier,
+            icon = icon,
+            title = title,
+            text = text,
+            shape = shape,
+            containerColor = containerColor,
+            tonalElevation = tonalElevation,
+            // Note that a button content color is provided here from the dialog's token, but in
+            // most cases, TextButtons should be used for dismiss and confirm buttons.
+            // TextButtons will not consume this provided content color value, and will used their
+            // own defined or default colors.
+            buttonContentColor = MaterialTheme.colorScheme.fromToken(
+                androidx.compose.material3.tokens.Dialog.ActionLabelTextColor
+            ),
+            iconContentColor = iconContentColor,
+            titleContentColor = titleContentColor,
+            textContentColor = textContentColor,
+        )
+    }
+}
+
+private val ButtonsMainAxisSpacing = 8.dp
+private val ButtonsCrossAxisSpacing = 12.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AlertDialog.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AlertDialog.kt
new file mode 100644
index 0000000..2e59296
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AlertDialog.kt
@@ -0,0 +1,223 @@
+/*
+ * 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.material3
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.material3.tokens.Dialog
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import kotlin.math.max
+
+@Composable
+internal fun AlertDialogContent(
+    buttons: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    icon: (@Composable () -> Unit)?,
+    title: (@Composable () -> Unit)?,
+    text: @Composable (() -> Unit)?,
+    shape: Shape,
+    containerColor: Color,
+    tonalElevation: Dp,
+    buttonContentColor: Color,
+    iconContentColor: Color,
+    titleContentColor: Color,
+    textContentColor: Color,
+) {
+    Surface(
+        modifier = modifier,
+        shape = shape,
+        color = containerColor,
+        tonalElevation = tonalElevation,
+    ) {
+        Column(
+            modifier = Modifier
+                .sizeIn(minWidth = MinWidth, maxWidth = MaxWidth)
+                .padding(DialogPadding)
+        ) {
+            icon?.let {
+                CompositionLocalProvider(LocalContentColor provides iconContentColor) {
+                    Box(
+                        Modifier
+                            .padding(IconPadding)
+                            .align(Alignment.CenterHorizontally)
+                    ) {
+                        icon()
+                    }
+                }
+            }
+            title?.let {
+                CompositionLocalProvider(LocalContentColor provides titleContentColor) {
+                    val textStyle = MaterialTheme.typography.fromToken(Dialog.SubheadFont)
+                    ProvideTextStyle(textStyle) {
+                        Box(
+                            // Align the title to the center when an icon is present.
+                            Modifier
+                                .padding(TitlePadding)
+                                .align(
+                                    if (icon == null) {
+                                        Alignment.Start
+                                    } else {
+                                        Alignment.CenterHorizontally
+                                    }
+                                )
+                        ) {
+                            title()
+                        }
+                    }
+                }
+            }
+            text?.let {
+                CompositionLocalProvider(LocalContentColor provides textContentColor) {
+                    val textStyle =
+                        MaterialTheme.typography.fromToken(Dialog.SupportingTextFont)
+                    ProvideTextStyle(textStyle) {
+                        Box(
+                            Modifier
+                                .padding(TextPadding)
+                                .align(Alignment.Start)
+                        ) {
+                            text()
+                        }
+                    }
+                }
+            }
+            Box(modifier = Modifier.align(Alignment.End)) {
+                CompositionLocalProvider(LocalContentColor provides buttonContentColor) {
+                    val textStyle =
+                        MaterialTheme.typography.fromToken(Dialog.ActionLabelTextFont)
+                    ProvideTextStyle(value = textStyle, content = buttons)
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Simple clone of FlowRow that arranges its children in a horizontal flow with limited
+ * customization.
+ */
+@Composable
+internal fun AlertDialogFlowRow(
+    mainAxisSpacing: Dp,
+    crossAxisSpacing: Dp,
+    content: @Composable () -> Unit
+) {
+    Layout(content) { measurables, constraints ->
+        val sequences = mutableListOf<List<Placeable>>()
+        val crossAxisSizes = mutableListOf<Int>()
+        val crossAxisPositions = mutableListOf<Int>()
+
+        var mainAxisSpace = 0
+        var crossAxisSpace = 0
+
+        val currentSequence = mutableListOf<Placeable>()
+        var currentMainAxisSize = 0
+        var currentCrossAxisSize = 0
+
+        // Return whether the placeable can be added to the current sequence.
+        fun canAddToCurrentSequence(placeable: Placeable) =
+            currentSequence.isEmpty() || currentMainAxisSize + mainAxisSpacing.roundToPx() +
+                placeable.width <= constraints.maxWidth
+
+        // Store current sequence information and start a new sequence.
+        fun startNewSequence() {
+            if (sequences.isNotEmpty()) {
+                crossAxisSpace += crossAxisSpacing.roundToPx()
+            }
+            sequences += currentSequence.toList()
+            crossAxisSizes += currentCrossAxisSize
+            crossAxisPositions += crossAxisSpace
+
+            crossAxisSpace += currentCrossAxisSize
+            mainAxisSpace = max(mainAxisSpace, currentMainAxisSize)
+
+            currentSequence.clear()
+            currentMainAxisSize = 0
+            currentCrossAxisSize = 0
+        }
+
+        for (measurable in measurables) {
+            // Ask the child for its preferred size.
+            val placeable = measurable.measure(constraints)
+
+            // Start a new sequence if there is not enough space.
+            if (!canAddToCurrentSequence(placeable)) startNewSequence()
+
+            // Add the child to the current sequence.
+            if (currentSequence.isNotEmpty()) {
+                currentMainAxisSize += mainAxisSpacing.roundToPx()
+            }
+            currentSequence.add(placeable)
+            currentMainAxisSize += placeable.width
+            currentCrossAxisSize = max(currentCrossAxisSize, placeable.height)
+        }
+
+        if (currentSequence.isNotEmpty()) startNewSequence()
+
+        val mainAxisLayoutSize = max(mainAxisSpace, constraints.minWidth)
+
+        val crossAxisLayoutSize = max(crossAxisSpace, constraints.minHeight)
+
+        val layoutWidth = mainAxisLayoutSize
+
+        val layoutHeight = crossAxisLayoutSize
+
+        layout(layoutWidth, layoutHeight) {
+            sequences.forEachIndexed { i, placeables ->
+                val childrenMainAxisSizes = IntArray(placeables.size) { j ->
+                    placeables[j].width +
+                        if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0
+                }
+                val arrangement = Arrangement.Bottom
+                // TODO(soboleva): rtl support
+                // Handle vertical direction
+                val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 }
+                with(arrangement) {
+                    arrange(mainAxisLayoutSize, childrenMainAxisSizes, mainAxisPositions)
+                }
+                placeables.forEachIndexed { j, placeable ->
+                    placeable.place(
+                        x = mainAxisPositions[j],
+                        y = crossAxisPositions[i]
+                    )
+                }
+            }
+        }
+    }
+}
+
+// Paddings for each of the dialog's parts.
+private val DialogPadding = PaddingValues(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 18.dp)
+private val IconPadding = PaddingValues(bottom = 16.dp)
+private val TitlePadding = PaddingValues(bottom = 16.dp)
+private val TextPadding = PaddingValues(bottom = 18.dp)
+
+private val MinWidth = 280.dp
+private val MaxWidth = 560.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/Dialog.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/Dialog.kt
new file mode 100644
index 0000000..66e5433
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/Dialog.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.unit.dp
+
+internal object Dialog {
+    val ActionLabelTextColor = ColorSchemeKey.Primary
+    val ActionLabelTextFont = TypographyKey.LabelLarge
+    val ContainerColor = ColorSchemeKey.Surface
+    val ContainerElevation = Elevation.Level3
+    val ContainerShape = RoundedCornerShape(28.0.dp)
+    val SubheadColor = ColorSchemeKey.OnSurface
+    val SubheadFont = TypographyKey.HeadlineSmall
+    val SupportingTextColor = ColorSchemeKey.OnSurfaceVariant
+    val SupportingTextFont = TypographyKey.BodyMedium
+    val WithDividerDividerColor = ColorSchemeKey.Outline
+    val WithDividerDividerHeight = 1.0.dp
+    val WithIconIconColor = ColorSchemeKey.Secondary
+    val WithIconIconSize = 24.0.dp
+}
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index c9a58a3..e12a0a1 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -62,6 +62,7 @@
     method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static int getCurrentCompositeKeyHash();
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.CompositionLocalContext getCurrentCompositionLocalContext();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
     method @androidx.compose.runtime.Composable public static inline <T> T! key(Object![]? keys, kotlin.jvm.functions.Function0<? extends T> block);
     method @androidx.compose.runtime.Composable public static inline <T> T! remember(kotlin.jvm.functions.Function0<? extends T> calculation);
@@ -170,8 +171,12 @@
     property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final inline T! current;
   }
 
+  @androidx.compose.runtime.Stable public final class CompositionLocalContext {
+  }
+
   public final class CompositionLocalKt {
     method @androidx.compose.runtime.Composable public static void CompositionLocalProvider(androidx.compose.runtime.ProvidedValue<?>![] values, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void CompositionLocalProvider(androidx.compose.runtime.CompositionLocalContext context, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method public static <T> androidx.compose.runtime.ProvidableCompositionLocal<T> compositionLocalOf(optional androidx.compose.runtime.SnapshotMutationPolicy<T> policy, kotlin.jvm.functions.Function0<? extends T> defaultFactory);
     method public static <T> androidx.compose.runtime.ProvidableCompositionLocal<T> staticCompositionLocalOf(kotlin.jvm.functions.Function0<? extends T> defaultFactory);
   }
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 029fb6d..8d3e08cd 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -62,6 +62,7 @@
     method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static int getCurrentCompositeKeyHash();
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.CompositionLocalContext getCurrentCompositionLocalContext();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
     method @androidx.compose.runtime.Composable public static inline <T> T! key(Object![]? keys, kotlin.jvm.functions.Function0<? extends T> block);
     method @androidx.compose.runtime.Composable public static inline <T> T! remember(kotlin.jvm.functions.Function0<? extends T> calculation);
@@ -179,8 +180,12 @@
     property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final inline T! current;
   }
 
+  @androidx.compose.runtime.Stable public final class CompositionLocalContext {
+  }
+
   public final class CompositionLocalKt {
     method @androidx.compose.runtime.Composable public static void CompositionLocalProvider(androidx.compose.runtime.ProvidedValue<?>![] values, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void CompositionLocalProvider(androidx.compose.runtime.CompositionLocalContext context, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method public static <T> androidx.compose.runtime.ProvidableCompositionLocal<T> compositionLocalOf(optional androidx.compose.runtime.SnapshotMutationPolicy<T> policy, kotlin.jvm.functions.Function0<? extends T> defaultFactory);
     method public static <T> androidx.compose.runtime.ProvidableCompositionLocal<T> staticCompositionLocalOf(kotlin.jvm.functions.Function0<? extends T> defaultFactory);
   }
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index d7a5712..e228621 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -63,6 +63,7 @@
     method @androidx.compose.runtime.Composable public static inline void ReusableContent(Object? key, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.Composer getCurrentComposer();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ExplicitGroupsComposable public static int getCurrentCompositeKeyHash();
+    method @androidx.compose.runtime.Composable public static androidx.compose.runtime.CompositionLocalContext getCurrentCompositionLocalContext();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public static androidx.compose.runtime.RecomposeScope getCurrentRecomposeScope();
     method @kotlin.PublishedApi internal static void invalidApplier();
     method @androidx.compose.runtime.Composable public static inline <T> T! key(Object![]? keys, kotlin.jvm.functions.Function0<? extends T> block);
@@ -185,8 +186,12 @@
     property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final inline T! current;
   }
 
+  @androidx.compose.runtime.Stable public final class CompositionLocalContext {
+  }
+
   public final class CompositionLocalKt {
     method @androidx.compose.runtime.Composable public static void CompositionLocalProvider(androidx.compose.runtime.ProvidedValue<?>![] values, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void CompositionLocalProvider(androidx.compose.runtime.CompositionLocalContext context, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method public static <T> androidx.compose.runtime.ProvidableCompositionLocal<T> compositionLocalOf(optional androidx.compose.runtime.SnapshotMutationPolicy<T> policy, kotlin.jvm.functions.Function0<? extends T> defaultFactory);
     method public static <T> androidx.compose.runtime.ProvidableCompositionLocal<T> staticCompositionLocalOf(kotlin.jvm.functions.Function0<? extends T> defaultFactory);
   }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
index 3636e210..169955c 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
@@ -168,6 +168,19 @@
     }
 
 /**
+ * Returns the current [CompositionLocalContext] which contains all
+ * [CompositionLocal]'s in the current composition and their values
+ * provided by [CompositionLocalProvider]'s.
+ * This context can be used to pass locals to another composition via [CompositionLocalProvider].
+ * That is usually needed if another composition is not a subcomposition of the current one.
+ */
+@OptIn(InternalComposeApi::class)
+val currentCompositionLocalContext: CompositionLocalContext
+    @Composable get() = CompositionLocalContext(
+        currentComposer.buildContext().getCompositionLocalScope()
+    )
+
+/**
  * This a hash value used to coordinate map externally stored state to the composition. For
  * example, this is used by saved instance state to preserve state across activity lifetime
  * boundaries.
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
index a7132b2..2e18cec 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
@@ -28,5 +28,5 @@
      * IMPORTANT: Whenever updating this value, please make sure to also update `versionTable` and
      * `minimumRuntimeVersionInt` in `VersionChecker.kt` of the compiler.
      */
-    const val version: Int = 4500
+    const val version: Int = 4600
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt
index 5799751..1031f82 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt
@@ -197,6 +197,19 @@
     StaticProvidableCompositionLocal(defaultFactory)
 
 /**
+ * Stores [CompositionLocal]'s and their values.
+ *
+ * Can be obtained via [currentCompositionLocalContext] and passed to another composition
+ * via [CompositionLocalProvider].
+ *
+ * [CompositionLocalContext] is immutable and won't be changed after its obtaining.
+ */
+@Stable
+class CompositionLocalContext internal constructor(
+    internal val compositionLocals: CompositionLocalMap
+)
+
+/**
  * [CompositionLocalProvider] binds values to [ProvidableCompositionLocal] keys. Reading the
  * [CompositionLocal] using [CompositionLocal.current] will return the value provided in
  * [CompositionLocalProvider]'s [values] parameter for all composable functions called directly
@@ -215,3 +228,27 @@
     content()
     currentComposer.endProviders()
 }
+
+/**
+ * [CompositionLocalProvider] binds values to [CompositionLocal]'s, provided by [context].
+ * Reading the [CompositionLocal] using [CompositionLocal.current] will return the value provided in
+ * values stored inside [context] for all composable functions called directly
+ * or indirectly in the [content] lambda.
+ *
+ * @sample androidx.compose.runtime.samples.compositionLocalProvider
+ *
+ * @see CompositionLocal
+ * @see compositionLocalOf
+ * @see staticCompositionLocalOf
+ */
+@Suppress("UNCHECKED_CAST")
+@Composable
+@OptIn(InternalComposeApi::class)
+fun CompositionLocalProvider(context: CompositionLocalContext, content: @Composable () -> Unit) {
+    CompositionLocalProvider(
+        *context.compositionLocals
+            .map { it.key as ProvidableCompositionLocal<Any?> provides it.value.value }
+            .toTypedArray(),
+        content = content
+    )
+}
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionLocalTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
index 638fff2..64c54a5 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
@@ -17,11 +17,15 @@
 package androidx.compose.runtime
 
 import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentHashMapOf
+import androidx.compose.runtime.mock.EmptyApplier
+import androidx.compose.runtime.mock.TestMonotonicFrameClock
 import androidx.compose.runtime.mock.Text
 import androidx.compose.runtime.mock.compositionTest
 import androidx.compose.runtime.mock.expectChanges
 import androidx.compose.runtime.mock.expectNoChanges
 import androidx.compose.runtime.mock.validate
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
 import kotlin.test.assertEquals
 import kotlin.test.Test
 import kotlin.test.assertFalse
@@ -560,6 +564,85 @@
             assertEquals(it, n[it])
         }
     }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun testProvideAllLocals() = compositionTest {
+        val local1 = compositionLocalOf { 0 }
+        val local2 = compositionLocalOf { 0 }
+        val staticLocal = staticCompositionLocalOf { 0 }
+
+        var provided: Array<ProvidedValue<Int>> by mutableStateOf(emptyArray())
+
+        var actualValues = emptySet<Any>()
+
+        @Composable
+        fun LocalsConsumer() {
+            val locals by rememberUpdatedState(currentCompositionLocalContext)
+            DisposableEffect(Unit) {
+                val context = coroutineContext + TestMonotonicFrameClock(this@compositionTest)
+                val recomposer = Recomposer(context)
+                launch(context) {
+                    recomposer.runRecomposeAndApplyChanges()
+                }
+                val composition2 = Composition(EmptyApplier(), recomposer)
+                composition2.setContent {
+                    CompositionLocalProvider(locals) {
+                        actualValues = setOf(
+                            local1.current,
+                            local2.current,
+                            staticLocal.current,
+                        )
+                    }
+                }
+                onDispose {
+                    composition2.dispose()
+                    recomposer.cancel()
+                }
+            }
+        }
+
+        compose {
+            CompositionLocalProvider(*provided) {
+                LocalsConsumer()
+            }
+        }
+
+        advance()
+        assertEquals(setOf(0, 0, 0), actualValues)
+
+        provided = arrayOf(local1 provides 1)
+        advance()
+        assertEquals(setOf(1, 0, 0), actualValues)
+
+        provided = arrayOf(local1 provides 2)
+        advance()
+        assertEquals(setOf(2, 0, 0), actualValues)
+
+        provided = arrayOf(local1 provides 2, staticLocal provides 1)
+        advance()
+        assertEquals(setOf(2, 0, 1), actualValues)
+
+        provided = arrayOf(local1 provides 2, staticLocal provides 2)
+        advance()
+        assertEquals(setOf(2, 0, 2), actualValues)
+
+        provided = arrayOf(local1 provides 1, staticLocal provides 1)
+        advance()
+        assertEquals(setOf(1, 0, 1), actualValues)
+
+        provided = arrayOf(local1 provides 1, local2 provides 1, staticLocal provides 1)
+        advance()
+        assertEquals(setOf(1, 1, 1), actualValues)
+
+        provided = arrayOf(local1 provides 1, staticLocal provides 1)
+        advance()
+        assertEquals(setOf(1, 0, 1), actualValues)
+
+        provided = emptyArray()
+        advance()
+        assertEquals(setOf(0, 0, 0), actualValues)
+    }
 }
 
 data class SomeData(val value: String = "default")
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt
index 9f873b7..36f11ee 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.mock.Contact
 import androidx.compose.runtime.mock.ContactModel
 import androidx.compose.runtime.mock.Edit
+import androidx.compose.runtime.mock.EmptyApplier
 import androidx.compose.runtime.mock.Linear
 import androidx.compose.runtime.mock.MockViewValidator
 import androidx.compose.runtime.mock.Point
@@ -3286,22 +3287,3 @@
 private interface Named {
     val name: String
 }
-
-private class EmptyApplier : Applier<Unit> {
-    override val current: Unit = Unit
-    override fun down(node: Unit) {}
-    override fun up() {}
-    override fun insertTopDown(index: Int, instance: Unit) {
-        error("Unexpected")
-    }
-    override fun insertBottomUp(index: Int, instance: Unit) {
-        error("Unexpected")
-    }
-    override fun remove(index: Int, count: Int) {
-        error("Unexpected")
-    }
-    override fun move(from: Int, to: Int, count: Int) {
-        error("Unexpected")
-    }
-    override fun clear() {}
-}
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/EmptyApplier.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/EmptyApplier.kt
new file mode 100644
index 0000000..81cd8ee
--- /dev/null
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/mock/EmptyApplier.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.runtime.mock
+
+import androidx.compose.runtime.Applier
+
+class EmptyApplier : Applier<Unit> {
+    override val current: Unit = Unit
+    override fun down(node: Unit) {}
+    override fun up() {}
+    override fun insertTopDown(index: Int, instance: Unit) {
+        error("Unexpected")
+    }
+    override fun insertBottomUp(index: Int, instance: Unit) {
+        error("Unexpected")
+    }
+    override fun remove(index: Int, count: Int) {
+        error("Unexpected")
+    }
+    override fun move(from: Int, to: Int, count: Int) {
+        error("Unexpected")
+    }
+    override fun clear() {}
+}
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
index 41c7a25..3595318 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Offset.kt
@@ -258,7 +258,7 @@
 val Offset.isUnspecified: Boolean get() = packedValue == Offset.Unspecified.packedValue
 
 /**
- * If this [Offset] [isSpecified] then this is returned, otherwise [block] is executed
+ * If this [Offset]&nbsp;[isSpecified] then this is returned, otherwise [block] is executed
  * and its result is returned.
  */
 inline fun Offset.takeOrElse(block: () -> Offset): Offset =
diff --git a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
index 449c160..517afbd 100644
--- a/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
+++ b/compose/ui/ui-geometry/src/commonMain/kotlin/androidx/compose/ui/geometry/Size.kt
@@ -86,7 +86,7 @@
         /**
          * A size whose [width] and [height] are unspecified. This is a sentinel
          * value used to initialize a non-null parameter.
-         * Access to width or height on an unspecified size is not allowed
+         * Access to width or height on an unspecified size is not allowed.
          */
         @Stable
         val Unspecified = Size(Float.NaN, Float.NaN)
@@ -159,7 +159,7 @@
     get() = packedValue == Size.Unspecified.packedValue
 
 /**
- * If this [Size] [isSpecified] then this is returned, otherwise [block] is executed
+ * If this [Size]&nbsp;[isSpecified] then this is returned, otherwise [block] is executed
  * and its result is returned.
  */
 inline fun Size.takeOrElse(block: () -> Size): Size =
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt
index fada0e7..183c479 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt
@@ -130,6 +130,7 @@
                     | [Disabled]
                     |  |-Node #X at (l=X, t=X, r=X, b=X)px
                     |    Role = 'Button'
+                    |    Focused = 'false'
                     |    Text = '[Button]'
                     |    Actions = [OnClick, GetTextLayoutResult]
                     |    MergeDescendants = 'true'
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt
index 28850dc..c800b19 100644
--- a/compose/ui/ui-unit/api/current.txt
+++ b/compose/ui/ui-unit/api/current.txt
@@ -80,13 +80,19 @@
     method public static inline float getWidth(androidx.compose.ui.unit.DpRect);
     method public static inline boolean isFinite(float);
     method public static inline boolean isSpecified(float);
+    method public static inline boolean isSpecified(long);
+    method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(float);
+    method public static inline boolean isUnspecified(long);
+    method public static inline boolean isUnspecified(long);
     method @androidx.compose.runtime.Stable public static float lerp(float start, float stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static inline float max(float a, float b);
     method @androidx.compose.runtime.Stable public static inline float min(float a, float b);
     method public static inline float takeOrElse(float, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.Dp> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpOffset> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpSize> block);
     method @androidx.compose.runtime.Stable public static inline operator float times(float, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(double, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(int, float other);
@@ -99,7 +105,9 @@
   }
 
   public static final class DpOffset.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
@@ -130,7 +138,9 @@
   }
 
   public static final class DpSize.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
diff --git a/compose/ui/ui-unit/api/public_plus_experimental_current.txt b/compose/ui/ui-unit/api/public_plus_experimental_current.txt
index d51cc83..284ff88 100644
--- a/compose/ui/ui-unit/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-unit/api/public_plus_experimental_current.txt
@@ -80,13 +80,19 @@
     method public static inline float getWidth(androidx.compose.ui.unit.DpRect);
     method public static inline boolean isFinite(float);
     method public static inline boolean isSpecified(float);
+    method public static inline boolean isSpecified(long);
+    method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(float);
+    method public static inline boolean isUnspecified(long);
+    method public static inline boolean isUnspecified(long);
     method @androidx.compose.runtime.Stable public static float lerp(float start, float stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static inline float max(float a, float b);
     method @androidx.compose.runtime.Stable public static inline float min(float a, float b);
     method public static inline float takeOrElse(float, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.Dp> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpOffset> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpSize> block);
     method @androidx.compose.runtime.Stable public static inline operator float times(float, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(double, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(int, float other);
@@ -99,7 +105,9 @@
   }
 
   public static final class DpOffset.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
@@ -130,7 +138,9 @@
   }
 
   public static final class DpSize.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt
index 9fba15f..d891eaf 100644
--- a/compose/ui/ui-unit/api/restricted_current.txt
+++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -80,13 +80,19 @@
     method public static inline float getWidth(androidx.compose.ui.unit.DpRect);
     method public static inline boolean isFinite(float);
     method public static inline boolean isSpecified(float);
+    method public static inline boolean isSpecified(long);
+    method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(float);
+    method public static inline boolean isUnspecified(long);
+    method public static inline boolean isUnspecified(long);
     method @androidx.compose.runtime.Stable public static float lerp(float start, float stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static inline float max(float a, float b);
     method @androidx.compose.runtime.Stable public static inline float min(float a, float b);
     method public static inline float takeOrElse(float, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.Dp> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpOffset> block);
+    method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.unit.DpSize> block);
     method @androidx.compose.runtime.Stable public static inline operator float times(float, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(double, float other);
     method @androidx.compose.runtime.Stable public static inline operator float times(int, float other);
@@ -99,7 +105,9 @@
   }
 
   public static final class DpOffset.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
@@ -130,7 +138,9 @@
   }
 
   public static final class DpSize.Companion {
+    method public long getUnspecified();
     method public long getZero();
+    property public final long Unspecified;
     property public final long Zero;
   }
 
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Density.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Density.kt
index 787be8a..1f03221 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Density.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Density.kt
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.geometry.isSpecified
 import kotlin.math.roundToInt
 
 /**
@@ -141,11 +142,19 @@
      * Convert a [DpSize] to a [Size].
      */
     @Stable
-    fun DpSize.toSize(): Size = Size(width.toPx(), height.toPx())
+    fun DpSize.toSize(): Size = if (isSpecified) {
+        Size(width.toPx(), height.toPx())
+    } else {
+        Size.Unspecified
+    }
 
     /**
      * Convert a [Size] to a [DpSize].
      */
     @Stable
-    fun Size.toDpSize(): DpSize = DpSize(width.toDp(), height.toDp())
+    fun Size.toDpSize(): DpSize = if (isSpecified) {
+        DpSize(width.toDp(), height.toDp())
+    } else {
+        DpSize.Unspecified
+    }
 }
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
index 3670a6d..9f294ec 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
@@ -265,15 +265,27 @@
      * The horizontal aspect of the offset in [Dp]
      */
     @Stable
-    /*inline*/ val x: Dp
-        get() = unpackFloat1(packedValue).dp
+        /*inline*/ val x: Dp
+        get() {
+            // Explicitly compare against packed values to avoid auto-boxing of DpOffset.Unspecified
+            check(this.packedValue != Unspecified.packedValue) {
+                "DpOffset is unspecified"
+            }
+            return unpackFloat1(packedValue).dp
+        }
 
     /**
      * The vertical aspect of the offset in [Dp]
      */
     @Stable
-    /*inline*/ val y: Dp
-        get() = unpackFloat2(packedValue).dp
+        /*inline*/ val y: Dp
+        get() {
+            // Explicitly compare against packed values to avoid auto-boxing of DpOffset.Unspecified
+            check(this.packedValue != Unspecified.packedValue) {
+                "DpOffset is unspecified"
+            }
+            return unpackFloat2(packedValue).dp
+        }
 
     /**
      * Returns a copy of this [DpOffset] instance optionally overriding the
@@ -296,17 +308,50 @@
         DpOffset(x + other.x, y + other.y)
 
     @Stable
-    override fun toString(): String = "($x, $y)"
+    override fun toString(): String =
+        if (isSpecified) {
+            "($x, $y)"
+        } else {
+            "DpOffset.Unspecified"
+        }
 
     companion object {
         /**
          * A [DpOffset] with 0 DP [x] and 0 DP [y] values.
          */
         val Zero = DpOffset(0.dp, 0.dp)
+
+        /**
+         * Represents an offset whose [x] and [y] are unspecified. This is usually a replacement for
+         * `null` when a primitive value is desired.
+         * Access to [x] or [y] on an unspecified offset is not allowed.
+         */
+        val Unspecified = DpOffset(Dp.Unspecified, Dp.Unspecified)
     }
 }
 
 /**
+ * `false` when this is [DpOffset.Unspecified].
+ */
+@Stable
+inline val DpOffset.isSpecified: Boolean
+    get() = packedValue != DpOffset.Unspecified.packedValue
+
+/**
+ * `true` when this is [DpOffset.Unspecified].
+ */
+@Stable
+inline val DpOffset.isUnspecified: Boolean
+    get() = packedValue == DpOffset.Unspecified.packedValue
+
+/**
+ * If this [DpOffset]&nbsp;[isSpecified] then this is returned, otherwise [block] is executed
+ * and its result is returned.
+ */
+inline fun DpOffset.takeOrElse(block: () -> DpOffset): DpOffset =
+    if (isSpecified) this else block()
+
+/**
  * Linearly interpolate between two [DpOffset]s.
  *
  * The [fraction] argument represents position on the timeline, with 0.0 meaning
@@ -338,15 +383,27 @@
      * The horizontal aspect of the Size in [Dp]
      */
     @Stable
-    /*inline*/ val width: Dp
-        get() = unpackFloat1(packedValue).dp
+        /*inline*/ val width: Dp
+        get() {
+            // Explicitly compare against packed values to avoid auto-boxing of DpSize.Unspecified
+            check(this.packedValue != Unspecified.packedValue) {
+                "DpSize is unspecified"
+            }
+            return unpackFloat1(packedValue).dp
+        }
 
     /**
      * The vertical aspect of the Size in [Dp]
      */
     @Stable
-    /*inline*/ val height: Dp
-        get() = unpackFloat2(packedValue).dp
+        /*inline*/ val height: Dp
+        get() {
+            // Explicitly compare against packed values to avoid auto-boxing of DpSize.Unspecified
+            check(this.packedValue != Unspecified.packedValue) {
+                "DpSize is unspecified"
+            }
+            return unpackFloat2(packedValue).dp
+        }
 
     /**
      * Returns a copy of this [DpSize] instance optionally overriding the
@@ -387,17 +444,50 @@
     operator fun div(other: Float): DpSize = DpSize(width / other, height / other)
 
     @Stable
-    override fun toString(): String = "$width x $height"
+    override fun toString(): String =
+        if (isSpecified) {
+            "$width x $height"
+        } else {
+            "DpSize.Unspecified"
+        }
 
     companion object {
         /**
          * A [DpSize] with 0 DP [width] and 0 DP [height] values.
          */
         val Zero = DpSize(0.dp, 0.dp)
+
+        /**
+         * A size whose [width] and [height] are unspecified. This is usually a replacement for
+         * `null` when a primitive value is desired.
+         * Access to [width] or [height] on an unspecified size is not allowed.
+         */
+        val Unspecified = DpSize(Dp.Unspecified, Dp.Unspecified)
     }
 }
 
 /**
+ * `false` when this is [DpSize.Unspecified].
+ */
+@Stable
+inline val DpSize.isSpecified: Boolean
+    get() = packedValue != DpSize.Unspecified.packedValue
+
+/**
+ * `true` when this is [DpSize.Unspecified].
+ */
+@Stable
+inline val DpSize.isUnspecified: Boolean
+    get() = packedValue == DpSize.Unspecified.packedValue
+
+/**
+ * If this [DpSize]&nbsp;[isSpecified] then this is returned, otherwise [block] is executed
+ * and its result is returned.
+ */
+inline fun DpSize.takeOrElse(block: () -> DpSize): DpSize =
+    if (isSpecified) this else block()
+
+/**
  * Returns the [DpOffset] of the center of the rect from the point of [0, 0]
  * with this [DpSize].
  */
diff --git a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DensityTest.kt b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DensityTest.kt
index d7a297e..bcb2400 100644
--- a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DensityTest.kt
+++ b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DensityTest.kt
@@ -115,4 +115,14 @@
     fun testSizeToDpSize() = with(density) {
         assertEquals(DpSize(1.dp, 3.dp), Size(2f, 6f).toDpSize())
     }
+
+    @Test
+    fun testDpSizeUnspecifiedToSize() = with(density) {
+        assertEquals(Size.Unspecified, DpSize.Unspecified.toSize())
+    }
+
+    @Test
+    fun testSizeUnspecifiedToDpSize() = with(density) {
+        assertEquals(DpSize.Unspecified, Size.Unspecified.toDpSize())
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpOffsetTest.kt b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpOffsetTest.kt
new file mode 100644
index 0000000..6e2b181
--- /dev/null
+++ b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpOffsetTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.unit
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class DpOffsetTest {
+    @Test
+    fun constructor() {
+        val size = DpOffset(x = 5.dp, y = 10.dp)
+        assertEquals(5.dp, size.x)
+        assertEquals(10.dp, size.y)
+    }
+
+    @Test
+    fun copy() {
+        val position = DpOffset(12.dp, 27.dp)
+        assertEquals(position, position.copy())
+    }
+
+    @Test
+    fun copyOverwritesX() {
+        val position = DpOffset(15.dp, 32.dp)
+        val copy = position.copy(x = 59.dp)
+        assertEquals(59.dp, copy.x)
+        assertEquals(32.dp, copy.y)
+    }
+
+    @Test
+    fun copyOverwritesY() {
+        val position = DpOffset(19.dp, 42.dp)
+        val copy = position.copy(y = 67.dp)
+        assertEquals(19.dp, copy.x)
+        assertEquals(67.dp, copy.y)
+    }
+
+    @Test
+    fun dpOffsetPlusDpOffset() {
+        val a = DpOffset(3.dp, 10.dp)
+        val b = DpOffset(5.dp, 8.dp)
+        assertEquals(DpOffset(8.dp, 18.dp), a + b)
+        assertEquals(DpOffset(8.dp, 18.dp), b + a)
+    }
+
+    @Test
+    fun dpOffsetMinusDpOffset() {
+        val a = DpOffset(3.dp, 10.dp)
+        val b = DpOffset(5.dp, 8.dp)
+        assertEquals(DpOffset((-2).dp, 2.dp), a - b)
+        assertEquals(DpOffset(2.dp, (-2).dp), b - a)
+    }
+
+    @Test
+    fun lerp() {
+        val a = DpOffset(3.dp, 10.dp)
+        val b = DpOffset(5.dp, 8.dp)
+        assertEquals(DpOffset(4.dp, 9.dp), lerp(a, b, 0.5f))
+        assertEquals(DpOffset(3.dp, 10.dp), lerp(a, b, 0f))
+        assertEquals(DpOffset(5.dp, 8.dp), lerp(a, b, 1f))
+    }
+
+    @Test
+    fun testIsSpecified() {
+        assertFalse(DpOffset.Unspecified.isSpecified)
+        assertTrue(DpOffset(1.dp, 1.dp).isSpecified)
+    }
+
+    @Test
+    fun testIsUnspecified() {
+        assertTrue(DpOffset.Unspecified.isUnspecified)
+        assertFalse(DpOffset(1.dp, 1.dp).isUnspecified)
+    }
+
+    @Test
+    fun testTakeOrElseTrue() {
+        assertTrue(DpOffset(1.dp, 1.dp).takeOrElse { DpOffset.Unspecified }.isSpecified)
+    }
+
+    @Test
+    fun testTakeOrElseFalse() {
+        assertTrue(DpOffset.Unspecified.takeOrElse { DpOffset(1.dp, 1.dp) }.isSpecified)
+    }
+
+    @Test
+    fun testToString() {
+        assertEquals("(1.0.dp, 1.0.dp)", DpOffset(1.dp, 1.dp).toString())
+    }
+
+    @Test
+    fun testUnspecifiedToString() {
+        assertEquals("DpOffset.Unspecified", DpOffset.Unspecified.toString())
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpSizeTest.kt b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpSizeTest.kt
index d6203f9..14cbd2e 100644
--- a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpSizeTest.kt
+++ b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpSizeTest.kt
@@ -17,6 +17,8 @@
 package androidx.compose.ui.unit
 
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 import org.junit.Test
 
 class DpSizeTest {
@@ -74,4 +76,36 @@
     fun dpRectSize() {
         assertEquals(DpSize(10.dp, 5.dp), DpRect(2.dp, 3.dp, 12.dp, 8.dp).size)
     }
+
+    @Test
+    fun testIsSpecified() {
+        assertFalse(DpSize.Unspecified.isSpecified)
+        assertTrue(DpSize(1.dp, 1.dp).isSpecified)
+    }
+
+    @Test
+    fun testIsUnspecified() {
+        assertTrue(DpSize.Unspecified.isUnspecified)
+        assertFalse(DpSize(1.dp, 1.dp).isUnspecified)
+    }
+
+    @Test
+    fun testTakeOrElseTrue() {
+        assertTrue(DpSize(1.dp, 1.dp).takeOrElse { DpSize.Unspecified }.isSpecified)
+    }
+
+    @Test
+    fun testTakeOrElseFalse() {
+        assertTrue(DpSize.Unspecified.takeOrElse { DpSize(1.dp, 1.dp) }.isSpecified)
+    }
+
+    @Test
+    fun testToString() {
+        assertEquals("1.0.dp x 1.0.dp", DpSize(1.dp, 1.dp).toString())
+    }
+
+    @Test
+    fun testUnspecifiedToString() {
+        assertEquals("DpSize.Unspecified", DpSize.Unspecified.toString())
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpTest.kt b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpTest.kt
index afe2c27..9061916 100644
--- a/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpTest.kt
+++ b/compose/ui/ui-unit/src/test/kotlin/androidx/compose/ui/unit/DpTest.kt
@@ -162,31 +162,6 @@
     }
 
     @Test
-    fun lerpPosition() {
-        val a = DpOffset(3.dp, 10.dp)
-        val b = DpOffset(5.dp, 8.dp)
-        assertEquals(DpOffset(4.dp, 9.dp), lerp(a, b, 0.5f))
-        assertEquals(DpOffset(3.dp, 10.dp), lerp(a, b, 0f))
-        assertEquals(DpOffset(5.dp, 8.dp), lerp(a, b, 1f))
-    }
-
-    @Test
-    fun positionMinus() {
-        val a = DpOffset(3.dp, 10.dp)
-        val b = DpOffset(5.dp, 8.dp)
-        assertEquals(DpOffset(-2.dp, 2.dp), a - b)
-        assertEquals(DpOffset(2.dp, -2.dp), b - a)
-    }
-
-    @Test
-    fun positionPlus() {
-        val a = DpOffset(3.dp, 10.dp)
-        val b = DpOffset(5.dp, 8.dp)
-        assertEquals(DpOffset(8.dp, 18.dp), a + b)
-        assertEquals(DpOffset(8.dp, 18.dp), b + a)
-    }
-
-    @Test
     fun dpRectConstructor() {
         assertEquals(
             DpRect(10.dp, 5.dp, 25.dp, 15.dp),
@@ -207,28 +182,6 @@
     }
 
     @Test
-    fun testPositionCopy() {
-        val position = DpOffset(12.dp, 27.dp)
-        assertEquals(position, position.copy())
-    }
-
-    @Test
-    fun testPositionCopyOverwriteX() {
-        val position = DpOffset(15.dp, 32.dp)
-        val copy = position.copy(x = 59.dp)
-        assertEquals(59.dp, copy.x)
-        assertEquals(32.dp, copy.y)
-    }
-
-    @Test
-    fun testPositionCopyOverwriteY() {
-        val position = DpOffset(19.dp, 42.dp)
-        val copy = position.copy(y = 67.dp)
-        assertEquals(19.dp, copy.x)
-        assertEquals(67.dp, copy.y)
-    }
-
-    @Test
     fun testIsSpecified() {
         Assert.assertFalse(Dp.Unspecified.isSpecified)
         assertTrue(Dp(1f).isSpecified)
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index a5f0b94..9849f6c 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -524,6 +524,10 @@
     method @androidx.compose.runtime.Composable public static void Path(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, optional int pathFillType, optional String name, optional androidx.compose.ui.graphics.Brush? fill, optional float fillAlpha, optional androidx.compose.ui.graphics.Brush? stroke, optional float strokeAlpha, optional float strokeLineWidth, optional int strokeLineCap, optional int strokeLineJoin, optional float strokeLineMiter, optional float trimPathStart, optional float trimPathEnd, optional float trimPathOffset);
   }
 
+  public interface VectorConfig {
+    method public default <T> T! getOrDefault(androidx.compose.ui.graphics.vector.VectorProperty<T> property, T? defaultValue);
+  }
+
   @androidx.compose.runtime.Immutable public final class VectorGroup extends androidx.compose.ui.graphics.vector.VectorNode implements java.lang.Iterable<androidx.compose.ui.graphics.vector.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
     method public operator androidx.compose.ui.graphics.vector.VectorNode get(int index);
     method public java.util.List<androidx.compose.ui.graphics.vector.PathNode> getClipPathData();
@@ -584,6 +588,7 @@
   }
 
   public final class VectorPainterKt {
+    method @androidx.compose.runtime.Composable public static void RenderVectorGroup(androidx.compose.ui.graphics.vector.VectorGroup group, optional java.util.Map<java.lang.String,? extends androidx.compose.ui.graphics.vector.VectorConfig> configs);
     method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.vector.VectorPainter rememberVectorPainter(float defaultWidth, float defaultHeight, optional float viewportWidth, optional float viewportHeight, optional String name, optional long tintColor, optional int tintBlendMode, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.vector.VectorPainter rememberVectorPainter(androidx.compose.ui.graphics.vector.ImageVector image);
     field public static final String RootGroupName = "VectorRootGroup";
@@ -620,6 +625,73 @@
     property public final float trimPathStart;
   }
 
+  public abstract sealed class VectorProperty<T> {
+  }
+
+  public static final class VectorProperty.Fill extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.Fill INSTANCE;
+  }
+
+  public static final class VectorProperty.FillAlpha extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.FillAlpha INSTANCE;
+  }
+
+  public static final class VectorProperty.PathData extends androidx.compose.ui.graphics.vector.VectorProperty<java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode>> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.PathData INSTANCE;
+  }
+
+  public static final class VectorProperty.PivotX extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.PivotX INSTANCE;
+  }
+
+  public static final class VectorProperty.PivotY extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.PivotY INSTANCE;
+  }
+
+  public static final class VectorProperty.Rotation extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.Rotation INSTANCE;
+  }
+
+  public static final class VectorProperty.ScaleX extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.ScaleX INSTANCE;
+  }
+
+  public static final class VectorProperty.ScaleY extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.ScaleY INSTANCE;
+  }
+
+  public static final class VectorProperty.Stroke extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.Stroke INSTANCE;
+  }
+
+  public static final class VectorProperty.StrokeAlpha extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.StrokeAlpha INSTANCE;
+  }
+
+  public static final class VectorProperty.StrokeLineWidth extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.StrokeLineWidth INSTANCE;
+  }
+
+  public static final class VectorProperty.TranslateX extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.TranslateX INSTANCE;
+  }
+
+  public static final class VectorProperty.TranslateY extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.TranslateY INSTANCE;
+  }
+
+  public static final class VectorProperty.TrimPathEnd extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.TrimPathEnd INSTANCE;
+  }
+
+  public static final class VectorProperty.TrimPathOffset extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.TrimPathOffset INSTANCE;
+  }
+
+  public static final class VectorProperty.TrimPathStart extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.TrimPathStart INSTANCE;
+  }
+
 }
 
 package androidx.compose.ui.graphics.vector.compat {
@@ -2737,13 +2809,13 @@
     ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
     method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
-    method @Deprecated public boolean getDismissOnClickOutside();
+    method public boolean getDismissOnClickOutside();
     method public boolean getExcludeFromSystemGesture();
     method public boolean getFocusable();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
     property public final boolean clippingEnabled;
     property public final boolean dismissOnBackPress;
-    property @Deprecated public final boolean dismissOnClickOutside;
+    property public final boolean dismissOnClickOutside;
     property public final boolean excludeFromSystemGesture;
     property public final boolean focusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index a0035e9..e494fab 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -616,7 +616,7 @@
     method @androidx.compose.runtime.Composable public static void Path(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, optional int pathFillType, optional String name, optional androidx.compose.ui.graphics.Brush? fill, optional float fillAlpha, optional androidx.compose.ui.graphics.Brush? stroke, optional float strokeAlpha, optional float strokeLineWidth, optional int strokeLineCap, optional int strokeLineJoin, optional float strokeLineMiter, optional float trimPathStart, optional float trimPathEnd, optional float trimPathOffset);
   }
 
-  @androidx.compose.ui.ExperimentalComposeUiApi public interface VectorConfig {
+  public interface VectorConfig {
     method public default <T> T! getOrDefault(androidx.compose.ui.graphics.vector.VectorProperty<T> property, T? defaultValue);
   }
 
@@ -680,7 +680,7 @@
   }
 
   public final class VectorPainterKt {
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi public static void RenderVectorGroup(androidx.compose.ui.graphics.vector.VectorGroup group, optional java.util.Map<java.lang.String,? extends androidx.compose.ui.graphics.vector.VectorConfig> configs);
+    method @androidx.compose.runtime.Composable public static void RenderVectorGroup(androidx.compose.ui.graphics.vector.VectorGroup group, optional java.util.Map<java.lang.String,? extends androidx.compose.ui.graphics.vector.VectorConfig> configs);
     method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.vector.VectorPainter rememberVectorPainter(float defaultWidth, float defaultHeight, optional float viewportWidth, optional float viewportHeight, optional String name, optional long tintColor, optional int tintBlendMode, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.vector.VectorPainter rememberVectorPainter(androidx.compose.ui.graphics.vector.ImageVector image);
     field public static final String RootGroupName = "VectorRootGroup";
@@ -717,7 +717,7 @@
     property public final float trimPathStart;
   }
 
-  @androidx.compose.ui.ExperimentalComposeUiApi public abstract sealed class VectorProperty<T> {
+  public abstract sealed class VectorProperty<T> {
   }
 
   public static final class VectorProperty.Fill extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush> {
@@ -2971,25 +2971,21 @@
   }
 
   @androidx.compose.runtime.Immutable public final class PopupProperties {
-    ctor @androidx.compose.ui.ExperimentalComposeUiApi public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.geometry.Offset,? super androidx.compose.ui.unit.IntRect,java.lang.Boolean> dismissOnOutsideClick, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled, optional boolean usePlatformDefaultWidth, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> updateAndroidWindowManagerFlags);
+    ctor @androidx.compose.ui.ExperimentalComposeUiApi public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled, optional boolean usePlatformDefaultWidth);
     ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
     method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
-    method @Deprecated public boolean getDismissOnClickOutside();
-    method @androidx.compose.ui.ExperimentalComposeUiApi public kotlin.jvm.functions.Function2<androidx.compose.ui.geometry.Offset,androidx.compose.ui.unit.IntRect,java.lang.Boolean> getDismissOnOutsideClick();
+    method public boolean getDismissOnClickOutside();
     method public boolean getExcludeFromSystemGesture();
     method public boolean getFocusable();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
-    method @androidx.compose.ui.ExperimentalComposeUiApi public kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Integer> getUpdateAndroidWindowManagerFlags();
     method @androidx.compose.ui.ExperimentalComposeUiApi public boolean getUsePlatformDefaultWidth();
     property public final boolean clippingEnabled;
     property public final boolean dismissOnBackPress;
-    property @Deprecated public final boolean dismissOnClickOutside;
-    property @androidx.compose.ui.ExperimentalComposeUiApi public final kotlin.jvm.functions.Function2<androidx.compose.ui.geometry.Offset,androidx.compose.ui.unit.IntRect,java.lang.Boolean> dismissOnOutsideClick;
+    property public final boolean dismissOnClickOutside;
     property public final boolean excludeFromSystemGesture;
     property public final boolean focusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
-    property @androidx.compose.ui.ExperimentalComposeUiApi public final kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Integer> updateAndroidWindowManagerFlags;
     property @androidx.compose.ui.ExperimentalComposeUiApi public final boolean usePlatformDefaultWidth;
   }
 
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index e34df74..736b3fb 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -524,6 +524,10 @@
     method @androidx.compose.runtime.Composable public static void Path(java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode> pathData, optional int pathFillType, optional String name, optional androidx.compose.ui.graphics.Brush? fill, optional float fillAlpha, optional androidx.compose.ui.graphics.Brush? stroke, optional float strokeAlpha, optional float strokeLineWidth, optional int strokeLineCap, optional int strokeLineJoin, optional float strokeLineMiter, optional float trimPathStart, optional float trimPathEnd, optional float trimPathOffset);
   }
 
+  public interface VectorConfig {
+    method public default <T> T! getOrDefault(androidx.compose.ui.graphics.vector.VectorProperty<T> property, T? defaultValue);
+  }
+
   @androidx.compose.runtime.Immutable public final class VectorGroup extends androidx.compose.ui.graphics.vector.VectorNode implements java.lang.Iterable<androidx.compose.ui.graphics.vector.VectorNode> kotlin.jvm.internal.markers.KMappedMarker {
     method public operator androidx.compose.ui.graphics.vector.VectorNode get(int index);
     method public java.util.List<androidx.compose.ui.graphics.vector.PathNode> getClipPathData();
@@ -584,6 +588,7 @@
   }
 
   public final class VectorPainterKt {
+    method @androidx.compose.runtime.Composable public static void RenderVectorGroup(androidx.compose.ui.graphics.vector.VectorGroup group, optional java.util.Map<java.lang.String,? extends androidx.compose.ui.graphics.vector.VectorConfig> configs);
     method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.vector.VectorPainter rememberVectorPainter(float defaultWidth, float defaultHeight, optional float viewportWidth, optional float viewportHeight, optional String name, optional long tintColor, optional int tintBlendMode, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static androidx.compose.ui.graphics.vector.VectorPainter rememberVectorPainter(androidx.compose.ui.graphics.vector.ImageVector image);
     field public static final String RootGroupName = "VectorRootGroup";
@@ -620,6 +625,73 @@
     property public final float trimPathStart;
   }
 
+  public abstract sealed class VectorProperty<T> {
+  }
+
+  public static final class VectorProperty.Fill extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.Fill INSTANCE;
+  }
+
+  public static final class VectorProperty.FillAlpha extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.FillAlpha INSTANCE;
+  }
+
+  public static final class VectorProperty.PathData extends androidx.compose.ui.graphics.vector.VectorProperty<java.util.List<? extends androidx.compose.ui.graphics.vector.PathNode>> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.PathData INSTANCE;
+  }
+
+  public static final class VectorProperty.PivotX extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.PivotX INSTANCE;
+  }
+
+  public static final class VectorProperty.PivotY extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.PivotY INSTANCE;
+  }
+
+  public static final class VectorProperty.Rotation extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.Rotation INSTANCE;
+  }
+
+  public static final class VectorProperty.ScaleX extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.ScaleX INSTANCE;
+  }
+
+  public static final class VectorProperty.ScaleY extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.ScaleY INSTANCE;
+  }
+
+  public static final class VectorProperty.Stroke extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.Stroke INSTANCE;
+  }
+
+  public static final class VectorProperty.StrokeAlpha extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.StrokeAlpha INSTANCE;
+  }
+
+  public static final class VectorProperty.StrokeLineWidth extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.StrokeLineWidth INSTANCE;
+  }
+
+  public static final class VectorProperty.TranslateX extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.TranslateX INSTANCE;
+  }
+
+  public static final class VectorProperty.TranslateY extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.TranslateY INSTANCE;
+  }
+
+  public static final class VectorProperty.TrimPathEnd extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.TrimPathEnd INSTANCE;
+  }
+
+  public static final class VectorProperty.TrimPathOffset extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.TrimPathOffset INSTANCE;
+  }
+
+  public static final class VectorProperty.TrimPathStart extends androidx.compose.ui.graphics.vector.VectorProperty<java.lang.Float> {
+    field public static final androidx.compose.ui.graphics.vector.VectorProperty.TrimPathStart INSTANCE;
+  }
+
 }
 
 package androidx.compose.ui.graphics.vector.compat {
@@ -2773,13 +2845,13 @@
     ctor public PopupProperties(optional boolean focusable, optional boolean dismissOnBackPress, optional boolean dismissOnClickOutside, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean excludeFromSystemGesture, optional boolean clippingEnabled);
     method public boolean getClippingEnabled();
     method public boolean getDismissOnBackPress();
-    method @Deprecated public boolean getDismissOnClickOutside();
+    method public boolean getDismissOnClickOutside();
     method public boolean getExcludeFromSystemGesture();
     method public boolean getFocusable();
     method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
     property public final boolean clippingEnabled;
     property public final boolean dismissOnBackPress;
-    property @Deprecated public final boolean dismissOnClickOutside;
+    property public final boolean dismissOnClickOutside;
     property public final boolean excludeFromSystemGesture;
     property public final boolean focusable;
     property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 5bfc528..4b86181 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -481,7 +481,10 @@
         )
         if (Build.VERSION.SDK_INT >= 26) {
             assertEquals(
-                listOf(AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY),
+                listOf(
+                    AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY,
+                    "androidx.compose.ui.semantics.testTag"
+                ),
                 accessibilityNodeInfo.availableExtraData
             )
         }
@@ -941,6 +944,16 @@
         assertEquals(expectedTopLeftInScreenCoords.y, rectF.top)
         assertEquals(expectedRectInLocalCoords.width, rectF.width())
         assertEquals(expectedRectInLocalCoords.height, rectF.height())
+
+        val testTagKey = "androidx.compose.ui.semantics.testTag"
+        provider.addExtraDataToAccessibilityNodeInfo(
+            textFieldNode.id,
+            info,
+            testTagKey,
+            argument
+        )
+        val testTagData = info.extras.getCharSequence(testTagKey)
+        assertEquals(tag, testTagData.toString())
     }
 
     @Test
@@ -2862,6 +2875,30 @@
         }
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+    fun progressSemantics_mergesSemantics_forTalkback() {
+        container.setContent {
+            Box(Modifier.progressSemantics(0.5f).testTag("box"))
+        }
+
+        val node = rule.onNodeWithTag("box").fetchSemanticsNode()
+        val info = provider.createAccessibilityNodeInfo(node.id)
+        assertEquals(info.isScreenReaderFocusable, true)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+    fun indeterminateProgressSemantics_mergesSemantics_forTalkback() {
+        container.setContent {
+            Box(Modifier.progressSemantics().testTag("box"))
+        }
+
+        val node = rule.onNodeWithTag("box").fetchSemanticsNode()
+        val info = provider.createAccessibilityNodeInfo(node.id)
+        assertEquals(info.isScreenReaderFocusable, true)
+    }
+
     private fun eventIndex(list: List<AccessibilityEvent>, event: AccessibilityEvent): Int {
         for (i in list.indices) {
             if (ReflectionEquals(list[i], null).matches(event)) {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupKeyboardTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupKeyboardTest.kt
deleted file mode 100644
index c27d118..0000000
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupKeyboardTest.kt
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * 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.window
-
-import android.os.Build
-import android.view.View
-import android.view.WindowInsets
-import android.view.WindowInsetsAnimation
-import android.view.WindowManager
-import androidx.annotation.RequiresApi
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.collectIsFocusedAsState
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.background
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.TestActivity
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performClick
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class PopupKeyboardTest {
-    @get:Rule
-    val rule = createAndroidComposeRule<TestActivity>()
-
-    private lateinit var view: View
-    private val timeout = 10_000L
-
-    private val testTag = "testedPopup"
-    private val TFTag = "TextField"
-
-    @OptIn(ExperimentalComposeUiApi::class)
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
-    @Test
-    fun flagAltFocusableIMForNotFocusableDoesNotCloseKeyboardTest() {
-        rule.setContentForTest {
-            BoxWithAnchorAndPopupForTest(
-                testTag,
-                TFTag,
-                PopupProperties(
-                    focusable = false,
-                    updateAndroidWindowManagerFlags = { flags ->
-                        flags or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
-                    }
-                )
-            )
-        }
-
-        // Popup should not exist
-        rule.onNodeWithTag(testTag).assertDoesNotExist()
-
-        // Click on the TextField
-        rule.onNodeWithTag(TFTag).performClick()
-
-        // Popup should be visible
-        rule.onNodeWithTag(testTag).assertIsDisplayed()
-        view.waitUntil(timeout) { view.isSoftwareKeyboardShown() }
-
-        rule.runOnIdle { Truth.assertThat(view.isSoftwareKeyboardShown()).isTrue() }
-    }
-
-    @OptIn(ExperimentalComposeUiApi::class)
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
-    @Test
-    fun flagAltFocusableIMForFocusableDoesNotCloseKeyboardTest() {
-        rule.setContentForTest {
-            BoxWithAnchorAndPopupForTest(
-                testTag,
-                TFTag,
-                PopupProperties(
-                    focusable = true,
-                    updateAndroidWindowManagerFlags = { flags ->
-                        flags or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
-                    }
-                )
-            )
-        }
-
-        // Popup should not be visible
-        rule.onNodeWithTag(testTag).assertDoesNotExist()
-
-        // Click on the TextField
-        rule.onNodeWithTag(TFTag).performClick()
-
-        // Popup should be visible
-        rule.onNodeWithTag(testTag).assertIsDisplayed()
-        view.waitUntil(timeout) { view.isSoftwareKeyboardShown() }
-
-        rule.runOnIdle {
-            Truth.assertThat(view.isSoftwareKeyboardShown()).isTrue()
-        }
-    }
-
-    @RequiresApi(Build.VERSION_CODES.R)
-    private fun ComposeContentTestRule.setContentForTest(composable: @Composable () -> Unit) {
-        setContent {
-            view = LocalView.current
-            composable()
-        }
-        // We experienced some flakiness in tests if the keyboard was visible at the start of the
-        // test. So we make sure that the keyboard is hidden at the start of every test.
-        runOnIdle {
-            if (view.isSoftwareKeyboardShown()) {
-                view.hideKeyboard()
-                view.waitUntil(timeout) { !view.isSoftwareKeyboardShown() }
-            }
-        }
-    }
-}
-
-@RequiresApi(Build.VERSION_CODES.R)
-private fun View.waitUntil(timeoutMillis: Long, condition: () -> Boolean) {
-    val latch = CountDownLatch(1)
-    rootView.setWindowInsetsAnimationCallback(
-        InsetAnimationCallback {
-            if (condition()) {
-                latch.countDown()
-            }
-        }
-    )
-    latch.await(timeoutMillis, TimeUnit.MILLISECONDS)
-}
-
-@RequiresApi(Build.VERSION_CODES.R)
-private class InsetAnimationCallback(val block: () -> Unit) :
-    WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
-
-    override fun onProgress(
-        insets: WindowInsets,
-        runningAnimations: MutableList<WindowInsetsAnimation>
-    ) = insets
-
-    override fun onEnd(animation: WindowInsetsAnimation) {
-        block()
-        super.onEnd(animation)
-    }
-}
-
-@RequiresApi(Build.VERSION_CODES.R)
-private fun View.isSoftwareKeyboardShown(): Boolean {
-    return rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())
-}
-
-@RequiresApi(Build.VERSION_CODES.R)
-private fun View.hideKeyboard() {
-    windowInsetsController?.hide(WindowInsets.Type.ime())
-}
-
-@Composable
-fun BoxWithAnchorAndPopupForTest(
-    popupTag: String,
-    textFieldTag: String,
-    popupProperties: PopupProperties,
-) {
-    val focusRequester = remember { FocusRequester() }
-    val interactionSource = remember { MutableInteractionSource() }
-    val isTextFieldFocused by interactionSource.collectIsFocusedAsState()
-
-    Box {
-        BasicTextField(
-            "test",
-            {},
-            modifier = Modifier.focusRequester(focusRequester).testTag(textFieldTag),
-            interactionSource = interactionSource
-        )
-        if (isTextFieldFocused) {
-            Popup(
-                alignment = Alignment.Center,
-                onDismissRequest = { },
-                properties = popupProperties
-            ) {
-                Box(Modifier.background(Color.Red).size(50.dp).testTag(popupTag))
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt
index b222c57..2fcaaa3 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt
@@ -18,30 +18,26 @@
 import android.view.View
 import android.view.View.MEASURED_STATE_TOO_SMALL
 import android.view.ViewGroup
-import android.view.WindowManager
-import androidx.compose.foundation.clickable
+import android.widget.FrameLayout
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.requiredWidth
-import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.boundsInRoot
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.node.Owner
+import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.TestActivity
@@ -56,6 +52,7 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.height
+import androidx.compose.ui.viewinterop.AndroidView
 import androidx.lifecycle.ViewTreeLifecycleOwner
 import androidx.test.espresso.Espresso
 import androidx.test.espresso.Root
@@ -368,63 +365,6 @@
 
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
-    fun isNotDismissedOnAnchorClick_withCustomDismissOnOutsideClick() {
-        var anchorPosition = Rect.Zero
-        rule.setContent {
-            var showPopup by remember { mutableStateOf(true) }
-            Box(Modifier.wrapContentSize()) {
-                if (showPopup) {
-                    Box(
-                        modifier = Modifier.size(100.dp).onGloballyPositioned { layoutCoordinates ->
-                            anchorPosition = layoutCoordinates.boundsInRoot()
-                        }
-                    )
-                    Popup(
-                        properties = PopupProperties(
-                            focusable = true,
-                            dismissOnOutsideClick = { offset, anchorBounds ->
-                                if (offset == null) false
-                                else {
-                                    offset.x < anchorBounds.left ||
-                                        offset.x > anchorBounds.right ||
-                                        offset.y < anchorBounds.top ||
-                                        offset.y > anchorBounds.bottom
-                                }
-                            }
-                        ),
-                        alignment = Alignment.Center,
-                        onDismissRequest = { showPopup = false }
-                    ) {
-                        Box(Modifier.size(50.dp).testTag(testTag))
-                    }
-                }
-            }
-        }
-
-        // Popup should be visible
-        rule.onNodeWithTag(testTag).assertIsDisplayed()
-
-        // Click on the anchor
-        UiDevice.getInstance(getInstrumentation()).click(
-            anchorPosition.center.x.toInt(),
-            anchorPosition.center.y.toInt()
-        )
-
-        // Popup should still be visible
-        rule.onNodeWithTag(testTag).assertIsDisplayed()
-
-        // Click outside the anchor
-        UiDevice.getInstance(getInstrumentation()).click(
-            anchorPosition.right.toInt() + 1,
-            anchorPosition.bottom.toInt() + 1
-        )
-
-        // Popup should not exist
-        rule.onNodeWithTag(testTag).assertDoesNotExist()
-    }
-
-    @OptIn(ExperimentalComposeUiApi::class)
-    @Test
     fun canFillScreenWidth_dependingOnProperty() {
         var box1Width = 0
         var box2Width = 0
@@ -506,53 +446,30 @@
         rule.popupMatches(testTag, matchesSize(30, 30))
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     @Test
-    fun propagatesClicksIfNonTouchModalIsSet() {
-        var wasBoxClicked = false
+    fun doesNotCrashWhenAnchorDetachedFirst() {
+        var parent: FrameLayout? = null
         rule.setContent {
-            Box(
-                Modifier.fillMaxSize().clickable {
-                    wasBoxClicked = true
+            AndroidView(
+                factory = {
+                    FrameLayout(it).apply {
+                        addView(ComposeView(it).apply {
+                            setContent {
+                                Box {
+                                    Popup { Box(Modifier.size(20.dp)) }
+                                }
+                            }
+                        })
+                    }.also { parent = it }
                 }
-            ) {
-                Popup(
-                    alignment = Alignment.TopStart,
-                    onDismissRequest = { },
-                    properties = PopupProperties(
-                        focusable = true,
-                        updateAndroidWindowManagerFlags = { flags ->
-                            flags or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        }
-                    )
-                ) {
-                    Box(Modifier.size(10.dp).testTag(testTag))
-                }
-            }
+            )
         }
 
-        // Popup should be visible
-        rule.onNodeWithTag(testTag).assertIsDisplayed()
-
-        // Click inside the popup
-        val insideX = with(rule.density) { 5.dp.toPx() }
-        val insideY = with(rule.density) { 5.dp.toPx() }
-        UiDevice.getInstance(getInstrumentation()).click(insideX.toInt(), insideY.toInt())
-
-        // Box should not have been clicked
         rule.runOnIdle {
-            assertThat(wasBoxClicked).isFalse()
+            parent!!.removeAllViews()
         }
 
-        // Click outside the popup
-        val outsideX = with(rule.density) { 100.dp.toPx() }
-        val outsideY = with(rule.density) { 100.dp.toPx() }
-        UiDevice.getInstance(getInstrumentation()).click(outsideX.toInt(), outsideY.toInt())
-
-        // Box should've been clicked
-        rule.runOnIdle {
-            assertThat(wasBoxClicked).isTrue()
-        }
+        // Should not have crashed.
     }
 
     private fun matchesSize(width: Int, height: Int): BoundedMatcher<View, View> {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index e9e168e..64cd3cf 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -117,6 +117,8 @@
         const val InvalidId = Integer.MIN_VALUE
         const val ClassName = "android.view.View"
         const val LogTag = "AccessibilityDelegate"
+        const val ExtraDataTestTagKey = "androidx.compose.ui.semantics.testTag"
+
         /**
          * Intent size limitations prevent sending over a megabyte of data. Limit
          * text length to 100K characters - 200KB.
@@ -650,13 +652,23 @@
                     AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_PAGE
             }
         }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !info.text.isNullOrEmpty() &&
-            semanticsNode.unmergedConfig.contains(SemanticsActions.GetTextLayoutResult)
-        ) {
-            AccessibilityNodeInfoVerificationHelperMethods.setAvailableExtraData(
-                info.unwrap(),
-                listOf(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)
-            )
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            val extraDataKeys: MutableList<String> = mutableListOf()
+            if (!info.text.isNullOrEmpty() &&
+                semanticsNode.unmergedConfig.contains(SemanticsActions.GetTextLayoutResult)
+            ) {
+                extraDataKeys.add(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)
+            }
+            if (semanticsNode.unmergedConfig.contains(SemanticsProperties.TestTag)) {
+                extraDataKeys.add(ExtraDataTestTagKey)
+            }
+
+            if (!extraDataKeys.isEmpty()) {
+                AccessibilityNodeInfoVerificationHelperMethods.setAvailableExtraData(
+                    info.unwrap(),
+                    extraDataKeys
+                )
+            }
         }
 
         val rangeInfo =
@@ -1379,6 +1391,13 @@
                 boundingRects.add(boundsOnScreen)
             }
             info.extras.putParcelableArray(extraDataKey, boundingRects.toTypedArray())
+        } else if (node.unmergedConfig.contains(SemanticsProperties.TestTag) &&
+            arguments != null && extraDataKey == ExtraDataTestTagKey
+        ) {
+            val testTag = node.unmergedConfig.getOrNull(SemanticsProperties.TestTag)
+            if (testTag != null) {
+                info.extras.putCharSequence(extraDataKey, testTag)
+            }
         }
     }
 
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
index 41a82ac2..0fe17b6 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
@@ -51,7 +51,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.R
 import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onSizeChanged
@@ -86,12 +85,8 @@
  * If true, pressing the back button will call onDismissRequest. Note that [focusable] must be
  * set to true in order to receive key events such as the back button - if the popup is not
  * focusable then this property does nothing.
- * @property dismissOnOutsideClick Whether the popup should be dismissed when a click outside
- * the popup happens. This lambda will be called for every click which is about to request popup
- * dismissal, and returns whether the dismiss request should happen or not. The lambda receives
- * the anchor bounds as well the click offset, relative to the application window.
- * Note the offset might be unknown (`null`) when the click happens on a different window
- * (not the main application window) and the touch position is obscured.
+ * @property dismissOnClickOutside Whether the popup can be dismissed by clicking outside the
+ * popup's bounds. If true, clicking outside the popup will call onDismissRequest.
  * @property securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the popup's
  * window.
  * @property excludeFromSystemGesture A flag to check whether to set the systemGestureExclusionRects.
@@ -102,37 +97,19 @@
  * The default value is true.
  * @property usePlatformDefaultWidth Whether the width of the popup's content should be limited to
  * the platform default, which is smaller than the screen width.
- * @property updateAndroidWindowManagerFlags Offers low-level control over the flags passed
- * by the [Popup] to the Android WindowManager. The parameter of the lambda is the flags
- * calculated from the [PopupProperties] values that result in WindowManager flags: e.g. focusable.
- * The return value will be the final flags, which will be passed to the Android WindowManager.
- * By default, it will leave the flags calculated from parameters unchanged.
- * This API should be used with caution, only in cases where the popup has very specific behaviour
- * requirements.
  */
 @Immutable
 class PopupProperties @ExperimentalComposeUiApi constructor(
     val focusable: Boolean = false,
     val dismissOnBackPress: Boolean = true,
-    @Suppress("EXPERIMENTAL_ANNOTATION_ON_WRONG_TARGET")
-    @get:ExperimentalComposeUiApi
-    val dismissOnOutsideClick: (Offset?, IntRect) -> Boolean = alwaysDismissOnOutsideClick,
+    val dismissOnClickOutside: Boolean = true,
     val securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
     val excludeFromSystemGesture: Boolean = true,
     val clippingEnabled: Boolean = true,
     @Suppress("EXPERIMENTAL_ANNOTATION_ON_WRONG_TARGET")
     @get:ExperimentalComposeUiApi
-    val usePlatformDefaultWidth: Boolean = false,
-    @Suppress("EXPERIMENTAL_ANNOTATION_ON_WRONG_TARGET")
-    @get:ExperimentalComposeUiApi
-    val updateAndroidWindowManagerFlags: (Int) -> Int = PreserveFlags
+    val usePlatformDefaultWidth: Boolean = false
 ) {
-    @Deprecated(
-        "Superseded by dismissOnOutsideClick",
-        level = DeprecationLevel.WARNING
-    )
-    val dismissOnClickOutside = dismissOnOutsideClick == alwaysDismissOnOutsideClick
-
     @OptIn(ExperimentalComposeUiApi::class)
     constructor(
         focusable: Boolean = false,
@@ -141,16 +118,14 @@
         securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
         excludeFromSystemGesture: Boolean = true,
         clippingEnabled: Boolean = true,
-    ) : this(
+    ) : this (
         focusable = focusable,
         dismissOnBackPress = dismissOnBackPress,
-        dismissOnOutsideClick =
-            if (dismissOnClickOutside) alwaysDismissOnOutsideClick else { _, _ -> false },
+        dismissOnClickOutside = dismissOnClickOutside,
         securePolicy = securePolicy,
         excludeFromSystemGesture = excludeFromSystemGesture,
         clippingEnabled = clippingEnabled,
-        usePlatformDefaultWidth = false,
-        updateAndroidWindowManagerFlags = PreserveFlags
+        usePlatformDefaultWidth = false
     )
 
     @OptIn(ExperimentalComposeUiApi::class)
@@ -160,12 +135,11 @@
 
         if (focusable != other.focusable) return false
         if (dismissOnBackPress != other.dismissOnBackPress) return false
-        if (dismissOnOutsideClick != other.dismissOnOutsideClick) return false
+        if (dismissOnClickOutside != other.dismissOnClickOutside) return false
         if (securePolicy != other.securePolicy) return false
         if (excludeFromSystemGesture != other.excludeFromSystemGesture) return false
         if (clippingEnabled != other.clippingEnabled) return false
         if (usePlatformDefaultWidth != other.usePlatformDefaultWidth) return false
-        if (updateAndroidWindowManagerFlags != other.updateAndroidWindowManagerFlags) return false
 
         return true
     }
@@ -175,22 +149,15 @@
         var result = dismissOnBackPress.hashCode()
         result = 31 * result + focusable.hashCode()
         result = 31 * result + dismissOnBackPress.hashCode()
-        result = 31 * result + dismissOnOutsideClick.hashCode()
+        result = 31 * result + dismissOnClickOutside.hashCode()
         result = 31 * result + securePolicy.hashCode()
         result = 31 * result + excludeFromSystemGesture.hashCode()
         result = 31 * result + clippingEnabled.hashCode()
         result = 31 * result + usePlatformDefaultWidth.hashCode()
-        result = 31 * result + updateAndroidWindowManagerFlags.hashCode()
         return result
     }
 }
 
-private val alwaysDismissOnOutsideClick = { _: Offset?, _: IntRect ->
-    true
-}
-
-private val PreserveFlags: (Int) -> Int = { flags -> flags }
-
 /**
  * Opens a popup with the given content.
  *
@@ -396,7 +363,8 @@
     popupId: UUID
 ) : AbstractComposeView(composeView.context),
     ViewRootForInspector,
-    ViewTreeObserver.OnGlobalLayoutListener {
+    ViewTreeObserver.OnGlobalLayoutListener,
+    View.OnAttachStateChangeListener {
     private val windowManager =
         composeView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
     private val params = createLayoutParams()
@@ -423,6 +391,8 @@
     // The window visible frame used for the last popup position calculation.
     private val previousWindowVisibleFrame = Rect()
     private val tmpWindowVisibleFrame = Rect()
+    // Whether the PopupLayout is currently listening to global layout changes.
+    private var isListeningToGlobalLayout = false
 
     override val subCompositionView: AbstractComposeView get() = this
 
@@ -431,7 +401,12 @@
         ViewTreeLifecycleOwner.set(this, ViewTreeLifecycleOwner.get(composeView))
         ViewTreeViewModelStoreOwner.set(this, ViewTreeViewModelStoreOwner.get(composeView))
         ViewTreeSavedStateRegistryOwner.set(this, ViewTreeSavedStateRegistryOwner.get(composeView))
-        composeView.viewTreeObserver.addOnGlobalLayoutListener(this)
+        // We are listening to composeView detach changes, in order to remove properly the
+        // global layout listener we add to its viewTreeObserver. We cannot just remove the
+        // listener in during popup's dismiss as this can happen only after the composeView was
+        // detached and no longer references the original viewTreeObserver.
+        composeView.addOnAttachStateChangeListener(this)
+        registerOnGlobalLayoutListener()
         // Set unique id for AbstractComposeView. This allows state restoration for the state
         // defined inside the Popup via rememberSaveable()
         setTag(R.id.compose_view_saveable_id_tag, "Popup:$popupId")
@@ -576,7 +551,6 @@
         setIsFocusable(properties.focusable)
         setSecurePolicy(properties.securePolicy)
         setClippingEnabled(properties.clippingEnabled)
-        applyNewFlags(properties.updateAndroidWindowManagerFlags(params.flags))
         superSetLayoutDirection(layoutDirection)
     }
 
@@ -622,7 +596,8 @@
      */
     fun dismiss() {
         ViewTreeLifecycleOwner.set(this, null)
-        composeView.viewTreeObserver.removeOnGlobalLayoutListener(this)
+        unregisterOnGlobalLayoutListener()
+        composeView.removeOnAttachStateChangeListener(this)
         windowManager.removeViewImmediate(this)
     }
 
@@ -631,39 +606,23 @@
      * users clicks outside the popup.
      */
     override fun onTouchEvent(event: MotionEvent?): Boolean {
-        event ?: return super.onTouchEvent(event)
-
+        if (!properties.dismissOnClickOutside) {
+            return super.onTouchEvent(event)
+        }
         // Note that this implementation is taken from PopupWindow. It actually does not seem to
         // matter whether we return true or false as some upper layer decides on whether the
         // event is propagated to other windows or not. So for focusable the event is consumed but
         // for not focusable it is propagated to other windows.
-        if (
-            (
-                (event.action == MotionEvent.ACTION_DOWN) &&
-                    (
-                        (event.x < 0) ||
-                            (event.x >= width) ||
-                            (event.y < 0) ||
-                            (event.y >= height)
-                        )
-                ) ||
-            event.action == MotionEvent.ACTION_OUTSIDE
+        if ((event?.action == MotionEvent.ACTION_DOWN) &&
+            ((event.x < 0) || (event.x >= width) || (event.y < 0) || (event.y >= height))
         ) {
-            val parentBounds = parentBounds
-            val shouldDismiss = parentBounds == null || properties.dismissOnOutsideClick(
-                if (event.x != 0f || event.y != 0f) {
-                    Offset(
-                        params.x + event.x,
-                        params.y + event.y
-                    )
-                } else null,
-                parentBounds
-            )
-            if (shouldDismiss) {
-                onDismissRequest?.invoke()
-                return true
-            }
+            onDismissRequest?.invoke()
+            return true
+        } else if (event?.action == MotionEvent.ACTION_OUTSIDE) {
+            onDismissRequest?.invoke()
+            return true
         }
+
         return super.onTouchEvent(event)
     }
 
@@ -694,18 +653,13 @@
                 WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES or
                     WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                     WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
-                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
-                    WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
                     WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or
-                    WindowManager.LayoutParams.FLAG_SPLIT_TOUCH or
-                    WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+                    WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                 ).inv()
 
             // Enables us to intercept outside clicks even when popup is not focusable
             flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
 
-            softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
-
             type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
 
             // Get the Window token from the parent view
@@ -730,6 +684,22 @@
         bottom = bottom
     )
 
+    override fun onViewAttachedToWindow(composeView: View) = registerOnGlobalLayoutListener()
+
+    override fun onViewDetachedFromWindow(composeView: View) = unregisterOnGlobalLayoutListener()
+
+    private fun registerOnGlobalLayoutListener() {
+        if (isListeningToGlobalLayout || !composeView.isAttachedToWindow) return
+        composeView.viewTreeObserver.addOnGlobalLayoutListener(this)
+        isListeningToGlobalLayout = true
+    }
+
+    private fun unregisterOnGlobalLayoutListener() {
+        if (!isListeningToGlobalLayout) return
+        composeView.viewTreeObserver.removeOnGlobalLayoutListener(this)
+        isListeningToGlobalLayout = false
+    }
+
     override fun onGlobalLayout() {
         // Update the position of the popup, in case getWindowVisibleDisplayFrame has changed.
         composeView.getWindowVisibleDisplayFrame(tmpWindowVisibleFrame)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
index f320a6f..5aa0142 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
@@ -26,7 +26,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCompositionContext
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.BlendMode
 import androidx.compose.ui.graphics.Brush
@@ -102,7 +101,6 @@
  *
  * @param [image] ImageVector used to create a vector graphic sub-composition
  */
-@OptIn(ExperimentalComposeUiApi::class)
 @Composable
 fun rememberVectorPainter(image: ImageVector) =
     rememberVectorPainter(
@@ -220,7 +218,6 @@
  * Represents one of the properties for PathComponent or GroupComponent that can be overwritten
  * when it is composed and drawn with [RenderVectorGroup].
  */
-@ExperimentalComposeUiApi
 sealed class VectorProperty<T> {
     object Rotation : VectorProperty<Float>()
     object PivotX : VectorProperty<Float>()
@@ -246,7 +243,6 @@
  * This can be passed to [RenderVectorGroup] to alter some property values when the [VectorGroup]
  * is rendered.
  */
-@ExperimentalComposeUiApi
 interface VectorConfig {
     fun <T> getOrDefault(property: VectorProperty<T>, defaultValue: T): T {
         return defaultValue
@@ -260,7 +256,6 @@
  * @param configs An optional map of [VectorConfig] to provide animation values. The keys are the
  * node names. The values are [VectorConfig] for that node.
  */
-@ExperimentalComposeUiApi
 @Composable
 fun RenderVectorGroup(
     group: VectorGroup,
@@ -353,4 +348,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt
index 0cc346a..504f69c 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt
@@ -16,7 +16,6 @@
 package androidx.compose.ui.awt
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionContext
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.window.DialogWindowScope
@@ -57,7 +56,6 @@
     fun setContent(
         content: @Composable DialogWindowScope.() -> Unit
     ) = setContent(
-        parentComposition = null,
         onPreviewKeyEvent = { false },
         onKeyEvent = { false },
         content = content
@@ -66,13 +64,6 @@
     /**
      * Composes the given composable into the ComposeDialog.
      *
-     * The new composition can be logically "linked" to an existing one, by providing a
-     * [parentComposition]. This will ensure that invalidations and CompositionLocals will flow
-     * through the two compositions as if they were not separate.
-     *
-     * @param parentComposition The parent composition reference to coordinate
-     * scheduling of composition updates.
-     * If null then default root composition will be used.
      * @param onPreviewKeyEvent This callback is invoked when the user interacts with the hardware
      * keyboard. It gives ancestors of a focused component the chance to intercept a [KeyEvent].
      * Return true to stop propagation of this event. If you return false, the key event will be
@@ -85,7 +76,6 @@
      */
     @ExperimentalComposeUiApi
     fun setContent(
-        parentComposition: CompositionContext? = null,
         onPreviewKeyEvent: ((KeyEvent) -> Boolean) = { false },
         onKeyEvent: ((KeyEvent) -> Boolean) = { false },
         content: @Composable DialogWindowScope.() -> Unit
@@ -94,7 +84,6 @@
             override val window: ComposeDialog get() = this@ComposeDialog
         }
         delegate.setContent(
-            parentComposition,
             onPreviewKeyEvent,
             onKeyEvent,
         ) {
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 6f11256..d2af98b 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
@@ -17,7 +17,6 @@
 package androidx.compose.ui.awt
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionContext
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.mouse.MouseScrollOrientation
@@ -208,7 +207,6 @@
     }
 
     fun setContent(
-        parentComposition: CompositionContext? = null,
         onPreviewKeyEvent: (ComposeKeyEvent) -> Boolean = { false },
         onKeyEvent: (ComposeKeyEvent) -> Boolean = { false },
         content: @Composable () -> Unit
@@ -218,7 +216,6 @@
         // (we don't know the real density if we have unattached component)
         _initContent = {
             scene.setContent(
-                parentComposition,
                 onPreviewKeyEvent = onPreviewKeyEvent,
                 onKeyEvent = onKeyEvent,
                 content = content
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt
index c94fb24..eefacdb 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt
@@ -16,7 +16,6 @@
 package androidx.compose.ui.awt
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionContext
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.window.FrameWindowScope
@@ -54,7 +53,6 @@
     fun setContent(
         content: @Composable FrameWindowScope.() -> Unit
     ) = setContent(
-        parentComposition = null,
         onPreviewKeyEvent = { false },
         onKeyEvent = { false },
         content = content
@@ -63,13 +61,6 @@
     /**
      * Composes the given composable into the ComposeWindow.
      *
-     * The new composition can be logically "linked" to an existing one, by providing a
-     * [parentComposition]. This will ensure that invalidations and CompositionLocals will flow
-     * through the two compositions as if they were not separate.
-     *
-     * @param parentComposition The parent composition reference to coordinate
-     * scheduling of composition updates.
-     * If null then default root composition will be used.
      * @param onPreviewKeyEvent This callback is invoked when the user interacts with the hardware
      * keyboard. It gives ancestors of a focused component the chance to intercept a [KeyEvent].
      * Return true to stop propagation of this event. If you return false, the key event will be
@@ -82,7 +73,6 @@
      */
     @ExperimentalComposeUiApi
     fun setContent(
-        parentComposition: CompositionContext? = null,
         onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
         onKeyEvent: (KeyEvent) -> Boolean = { false },
         content: @Composable FrameWindowScope.() -> Unit
@@ -91,7 +81,6 @@
             override val window: ComposeWindow get() = this@ComposeWindow
         }
         delegate.setContent(
-            parentComposition,
             onPreviewKeyEvent,
             onKeyEvent
         ) {
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowDelegate.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowDelegate.desktop.kt
index 4fa38d2..83223831 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowDelegate.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowDelegate.desktop.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.awt
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionContext
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.window.LocalWindow
@@ -78,13 +77,11 @@
     }
 
     fun setContent(
-        parentComposition: CompositionContext? = null,
         onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
         onKeyEvent: (KeyEvent) -> Boolean = { false },
         content: @Composable () -> Unit
     ) {
         layer.setContent(
-            parentComposition = parentComposition,
             onPreviewKeyEvent = onPreviewKeyEvent,
             onKeyEvent = onKeyEvent,
         ) {
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Dialog.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Dialog.desktop.kt
index 8652227..1cf9d71 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Dialog.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Dialog.desktop.kt
@@ -17,9 +17,10 @@
 package androidx.compose.ui.window
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.currentCompositionLocalContext
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCompositionContext
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.awt.ComposeDialog
@@ -214,12 +215,16 @@
     update: (ComposeDialog) -> Unit = {},
     content: @Composable DialogWindowScope.() -> Unit
 ) {
-    val composition = rememberCompositionContext()
+    val currentLocals by rememberUpdatedState(currentCompositionLocalContext)
     AwtWindow(
         visible = visible,
         create = {
             create().apply {
-                setContent(composition, onPreviewKeyEvent, onKeyEvent, content)
+                setContent(onPreviewKeyEvent, onKeyEvent) {
+                    CompositionLocalProvider(currentLocals) {
+                        content()
+                    }
+                }
             }
         },
         dispose = dispose,
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Window.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Window.desktop.kt
index 40d2782..b80c265 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Window.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/Window.desktop.kt
@@ -17,7 +17,9 @@
 package androidx.compose.ui.window
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.currentCompositionLocalContext
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCompositionContext
@@ -305,12 +307,16 @@
     update: (ComposeWindow) -> Unit = {},
     content: @Composable FrameWindowScope.() -> Unit
 ) {
-    val composition = rememberCompositionContext()
+    val currentLocals by rememberUpdatedState(currentCompositionLocalContext)
     AwtWindow(
         visible = visible,
         create = {
             create().apply {
-                setContent(composition, onPreviewKeyEvent, onKeyEvent, content)
+                setContent(onPreviewKeyEvent, onKeyEvent) {
+                    CompositionLocalProvider(currentLocals) {
+                        content()
+                    }
+                }
             }
         },
         dispose = dispose,
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/ApplicationTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/ApplicationTest.kt
index bcd2437..9a5f7c9 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/ApplicationTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/ApplicationTest.kt
@@ -20,7 +20,10 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MonotonicFrameClock
+import androidx.compose.runtime.monotonicFrameClock
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -122,4 +125,30 @@
         assertThat(window1?.isShowing).isFalse()
         assertThat(window2?.isShowing).isFalse()
     }
+
+    @OptIn(ExperimentalComposeApi::class)
+    @Test
+    fun `window shouldn't use MonotonicFrameClock from application context`() = runApplicationTest {
+        lateinit var appClock: MonotonicFrameClock
+        lateinit var windowClock: MonotonicFrameClock
+
+        launchApplication {
+            LaunchedEffect(Unit) {
+                appClock = coroutineContext.monotonicFrameClock
+            }
+
+            Window(
+                onCloseRequest = {}
+            ) {
+                LaunchedEffect(Unit) {
+                    windowClock = coroutineContext.monotonicFrameClock
+                }
+            }
+        }
+
+        awaitIdle()
+        assertThat(windowClock).isNotEqualTo(appClock)
+
+        exitApplication()
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/TestUtils.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/TestUtils.kt
index af654a3..88e7d08 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/TestUtils.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/TestUtils.kt
@@ -16,14 +16,18 @@
 
 package androidx.compose.ui.window
 
+import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.takeWhile
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.swing.Swing
 import kotlinx.coroutines.withTimeout
@@ -103,6 +107,7 @@
     private val useDelay: Boolean
 ) : CoroutineScope by CoroutineScope(scope.coroutineContext + Job()) {
     var isOpen by mutableStateOf(true)
+    private val initialRecomposers = Recomposer.runningRecomposers.value
 
     fun exitApplication() {
         isOpen = false
@@ -122,5 +127,10 @@
         repeat(100) {
             yield()
         }
+
+        Snapshot.sendApplyNotifications()
+        for (recomposerInfo in Recomposer.runningRecomposers.value - initialRecomposers) {
+            recomposerInfo.state.takeWhile { it > Recomposer.State.Idle }.collect()
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/window/WindowTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/window/WindowTest.kt
index 5600ef9..85fb6b6 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/window/WindowTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/window/window/WindowTest.kt
@@ -305,21 +305,24 @@
     fun `pass composition local to windows`() = runApplicationTest {
         var actualValue1: Int? = null
         var actualValue2: Int? = null
+        var actualValue3: Int? = null
 
         var isOpen by mutableStateOf(true)
-        var testValue by mutableStateOf(0)
-        val localTestValue = compositionLocalOf { testValue }
+        val local1TestValue = compositionLocalOf { 0 }
+        val local2TestValue = compositionLocalOf { 0 }
+        var locals by mutableStateOf(arrayOf(local1TestValue provides 1))
 
         launchApplication {
             if (isOpen) {
-                CompositionLocalProvider(localTestValue provides testValue) {
+                CompositionLocalProvider(*locals) {
                     Window(
                         onCloseRequest = {},
                         state = rememberWindowState(
                             size = DpSize(600.dp, 600.dp),
                         )
                     ) {
-                        actualValue1 = localTestValue.current
+                        actualValue1 = local1TestValue.current
+                        actualValue2 = local2TestValue.current
                         Box(Modifier.size(32.dp).background(Color.Red))
 
                         Window(
@@ -328,7 +331,7 @@
                                 size = DpSize(300.dp, 300.dp),
                             )
                         ) {
-                            actualValue2 = localTestValue.current
+                            actualValue3 = local1TestValue.current
                             Box(Modifier.size(32.dp).background(Color.Blue))
                         }
                     }
@@ -337,13 +340,33 @@
         }
 
         awaitIdle()
-        assertThat(actualValue1).isEqualTo(0)
+        assertThat(actualValue1).isEqualTo(1)
         assertThat(actualValue2).isEqualTo(0)
+        assertThat(actualValue3).isEqualTo(1)
 
-        testValue = 42
+        locals = arrayOf(local1TestValue provides 42)
         awaitIdle()
         assertThat(actualValue1).isEqualTo(42)
-        assertThat(actualValue2).isEqualTo(42)
+        assertThat(actualValue2).isEqualTo(0)
+        assertThat(actualValue3).isEqualTo(42)
+
+        locals = arrayOf(local1TestValue provides 43)
+        awaitIdle()
+        assertThat(actualValue1).isEqualTo(43)
+        assertThat(actualValue2).isEqualTo(0)
+        assertThat(actualValue3).isEqualTo(43)
+
+        locals = arrayOf(local1TestValue provides 43, local2TestValue provides 12)
+        awaitIdle()
+        assertThat(actualValue1).isEqualTo(43)
+        assertThat(actualValue2).isEqualTo(12)
+        assertThat(actualValue3).isEqualTo(43)
+
+        locals = emptyArray()
+        awaitIdle()
+        assertThat(actualValue1).isEqualTo(0)
+        assertThat(actualValue2).isEqualTo(0)
+        assertThat(actualValue3).isEqualTo(0)
 
         isOpen = false
     }
diff --git a/core/core-appdigest/lint-baseline.xml b/core/core-appdigest/lint-baseline.xml
index d764360..0ca1950 100644
--- a/core/core-appdigest/lint-baseline.xml
+++ b/core/core-appdigest/lint-baseline.xml
@@ -23,4 +23,59 @@
             column="35"/>
     </issue>
 
+    <issue
+        id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="                                checksums[i] = new Checksum(apkChecksum.getSplitName(),"
+        errorLine2="                                                                        ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java"
+            line="121"
+            column="73"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="                                        apkChecksum.getType(), apkChecksum.getValue(),"
+        errorLine2="                                                    ~~~~~~~">
+        <location
+            file="src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java"
+            line="122"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="                                        apkChecksum.getType(), apkChecksum.getValue(),"
+        errorLine2="                                                                           ~~~~~~~~">
+        <location
+            file="src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java"
+            line="122"
+            column="76"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="                                        apkChecksum.getInstallerPackageName(),"
+        errorLine2="                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java"
+            line="123"
+            column="53"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class null is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="                                        apkChecksum.getInstallerCertificate());"
+        errorLine2="                                                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java"
+            line="124"
+            column="53"/>
+    </issue>
+
 </issues>
diff --git a/core/core-ktx/lint-baseline.xml b/core/core-ktx/lint-baseline.xml
index 5467d6f..2806442 100644
--- a/core/core-ktx/lint-baseline.xml
+++ b/core/core-ktx/lint-baseline.xml
@@ -47,6 +47,17 @@
 
     <issue
         id="NewApi"
+        message="Call requires API level S (current min is 14): `android.util.SparseArray#set`"
+        errorLine1="        array[1] = &quot;one&quot;"
+        errorLine2="             ~">
+        <location
+            file="src/androidTest/java/androidx/core/util/SparseArrayTest.kt"
+            line="61"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 17 (current min is 14): `updatePaddingRelative`"
         errorLine1="        view.updatePaddingRelative(start = 10, end = 20)"
         errorLine2="             ~~~~~~~~~~~~~~~~~~~~~">
diff --git a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingA11yScrollTest.java b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingA11yScrollTest.java
index 208c1201..8b80cfa 100644
--- a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingA11yScrollTest.java
+++ b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewNestedScrollingA11yScrollTest.java
@@ -44,6 +44,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.jetbrains.annotations.NotNull;
 import org.junit.Before;
@@ -67,7 +68,8 @@
     private static final int PARENT_HEIGHT = 300;
     private static final int WIDTH = 400;
     // A11Y scroll only scrolls the height of the NestedScrollView at max.
-    private static final int TOTAL_SCROLL_OFFSET = 100;
+    private static final int TOTAL_SCROLL_OFFSET = NSV_HEIGHT;
+    private static final int TOTAL_SCROLL_OFFSET_HALF = NSV_HEIGHT / 2;
 
     private NestedScrollView mNestedScrollView;
     private NestedScrollingSpyView mParent;
@@ -115,31 +117,60 @@
     @Test
     @SdkSuppress(minSdkVersion = 16)
     public void a11yActionScrollForward_fullyParticipatesInNestedScrolling() throws Throwable {
-        a11yScroll_fullyParticipatesInNestedScrolling(true);
+        a11yScroll_fullyParticipatesInNestedScrolling(true, /* startY= */ 0, TOTAL_SCROLL_OFFSET);
+    }
+
+    // minSdkVersion = 16 because View.performAccessibilityAction wasn't available till then.
+    @Test
+    @SdkSuppress(minSdkVersion = 16)
+    public void a11yActionScrollForward_halfVisibleSize_fullyParticipatesInNestedScrollingHalf()
+            throws Throwable {
+        mActivityTestRule.runOnUiThread(
+                () -> mParent.setLayoutParams(new FrameLayout.LayoutParams(WIDTH,
+                        TOTAL_SCROLL_OFFSET_HALF)));
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        a11yScroll_fullyParticipatesInNestedScrolling(true, /* startY= */ 0,
+                TOTAL_SCROLL_OFFSET_HALF);
     }
 
     // minSdkVersion = 16 because View.performAccessibilityAction wasn't available till then.
     @Test
     @SdkSuppress(minSdkVersion = 16)
     public void a11yActionScrollBackward_fullyParticipatesInNestedScrolling() throws Throwable {
-        a11yScroll_fullyParticipatesInNestedScrolling(false);
+        a11yScroll_fullyParticipatesInNestedScrolling(false, /* startY= */ 200,
+                -TOTAL_SCROLL_OFFSET);
     }
 
-    private void a11yScroll_fullyParticipatesInNestedScrolling(final boolean forward)
+    // minSdkVersion = 16 because View.performAccessibilityAction wasn't available till then.
+    @Test
+    @SdkSuppress(minSdkVersion = 16)
+    public void a11yActionScrollBackward_halfVisibleSize_fullyParticipatesInNestedScrollingHalf()
             throws Throwable {
+        mActivityTestRule.runOnUiThread(
+                () -> mParent.setLayoutParams(new FrameLayout.LayoutParams(WIDTH,
+                        TOTAL_SCROLL_OFFSET_HALF)));
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
+        a11yScroll_fullyParticipatesInNestedScrolling(false, /* startY= */ 200,
+                -TOTAL_SCROLL_OFFSET_HALF);
+    }
+
+    private void a11yScroll_fullyParticipatesInNestedScrolling(final boolean forward,
+            final int startY, final int expectedScrollOffset)
+            throws Throwable {
         final CountDownLatch countDownLatch = new CountDownLatch(2);
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
+                mNestedScrollView.scrollTo(0, startY);
                 doReturn(true).when(mParent).onStartNestedScroll(any(View.class), any(View.class),
                         anyInt(), anyInt());
-
                 int action;
+
                 if (forward) {
                     action = AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
                 } else {
-                    mNestedScrollView.scrollTo(0, 200);
                     action = AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD;
                 }
 
@@ -158,7 +189,7 @@
                             @Override
                             public void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
                                     int oldScrollX, int oldScrollY) {
-                                if (scrollY == TOTAL_SCROLL_OFFSET) {
+                                if ((scrollY - startY) == expectedScrollOffset) {
                                     countDownLatch.countDown();
                                 }
                             }
diff --git a/core/core/src/main/java/androidx/core/location/LocationManagerCompat.java b/core/core/src/main/java/androidx/core/location/LocationManagerCompat.java
index 4c84e98..495e55b 100644
--- a/core/core/src/main/java/androidx/core/location/LocationManagerCompat.java
+++ b/core/core/src/main/java/androidx/core/location/LocationManagerCompat.java
@@ -61,7 +61,6 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.WeakHashMap;
 import java.util.concurrent.ExecutionException;
@@ -221,9 +220,8 @@
     }
 
     @GuardedBy("sLocationListeners")
-    static final WeakHashMap<LocationListener,
-            List<WeakReference<LocationListenerTransport>>> sLocationListeners =
-            new WeakHashMap<>();
+    static final WeakHashMap<LocationListenerKey, WeakReference<LocationListenerTransport>>
+            sLocationListeners = new WeakHashMap<>();
 
     /**
      * Register for location updates from the specified provider, using a
@@ -268,7 +266,8 @@
             }
         }
 
-        LocationListenerTransport transport = new LocationListenerTransport(listener, executor);
+        LocationListenerTransport transport = new LocationListenerTransport(
+                new LocationListenerKey(provider, listener), executor);
 
         if (VERSION.SDK_INT >= 19) {
             try {
@@ -284,7 +283,7 @@
                     synchronized (sLocationListeners) {
                         sRequestLocationUpdatesLooperMethod.invoke(locationManager, request,
                                 transport, Looper.getMainLooper());
-                        transport.register();
+                        registerLocationListenerTransport(locationManager, transport);
                         return;
                     }
                 }
@@ -299,7 +298,20 @@
             locationManager.requestLocationUpdates(provider, locationRequest.getIntervalMillis(),
                     locationRequest.getMinUpdateDistanceMeters(), transport,
                     Looper.getMainLooper());
-            transport.register();
+            registerLocationListenerTransport(locationManager, transport);
+        }
+    }
+
+    @GuardedBy("sLocationListeners")
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    private static void registerLocationListenerTransport(LocationManager locationManager,
+            LocationListenerTransport transport) {
+        WeakReference<LocationListenerTransport> oldRef =
+                sLocationListeners.put(transport.getKey(), new WeakReference<>(transport));
+        LocationListenerTransport oldTransport = oldRef != null ? oldRef.get() : null;
+        if (oldTransport != null) {
+            oldTransport.unregister();
+            locationManager.removeUpdates(oldTransport);
         }
     }
 
@@ -360,14 +372,26 @@
     public static void removeUpdates(@NonNull LocationManager locationManager,
             @NonNull LocationListenerCompat listener) {
         synchronized (sLocationListeners) {
-            List<WeakReference<LocationListenerTransport>> transports =
-                    sLocationListeners.remove(listener);
-            if (transports != null) {
-                for (WeakReference<LocationListenerTransport> reference : transports) {
-                    LocationListenerTransport transport = reference.get();
-                    if (transport != null && transport.unregister()) {
-                        locationManager.removeUpdates(transport);
+            ArrayList<LocationListenerKey> cleanup = null;
+            for (WeakReference<LocationListenerTransport> transportRef :
+                    sLocationListeners.values()) {
+                LocationListenerTransport transport = transportRef.get();
+                if (transport == null) {
+                    continue;
+                }
+                LocationListenerKey key = transport.getKey();
+                if (key.mListener == listener) {
+                    if (cleanup == null) {
+                        cleanup = new ArrayList<>();
                     }
+                    cleanup.add(key);
+                    transport.unregister();
+                    locationManager.removeUpdates(transport);
+                }
+            }
+            if (cleanup != null) {
+                for (LocationListenerKey key : cleanup) {
+                    sLocationListeners.remove(key);
                 }
             }
         }
@@ -599,157 +623,137 @@
 
     private LocationManagerCompat() {}
 
+    private static class LocationListenerKey {
+        final String mProvider;
+        final LocationListenerCompat mListener;
+
+        LocationListenerKey(String provider,
+                LocationListenerCompat listener) {
+            mProvider = ObjectsCompat.requireNonNull(provider, "invalid null provider");
+            mListener = ObjectsCompat.requireNonNull(listener, "invalid null listener");
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof LocationListenerKey)) {
+                return false;
+            }
+
+            LocationListenerKey that = (LocationListenerKey) o;
+            return mProvider.equals(that.mProvider) && mListener.equals(that.mListener);
+        }
+
+        @Override
+        public int hashCode() {
+            return ObjectsCompat.hash(mProvider, mListener);
+        }
+    }
+
     private static class LocationListenerTransport implements LocationListener {
 
-        @Nullable volatile LocationListenerCompat mListener;
+        @Nullable volatile LocationListenerKey mKey;
         final Executor mExecutor;
 
-        LocationListenerTransport(@Nullable LocationListenerCompat listener, Executor executor) {
-            mListener = ObjectsCompat.requireNonNull(listener, "invalid null listener");
+        LocationListenerTransport(LocationListenerKey key, Executor executor) {
+            mKey = key;
             mExecutor = executor;
         }
 
-        @GuardedBy("sLocationListeners")
-        public void register() {
-            List<WeakReference<LocationListenerTransport>> transports =
-                    sLocationListeners.get(mListener);
-            if (transports == null) {
-                transports = new ArrayList<>(1);
-                sLocationListeners.put(mListener, transports);
-            } else {
-                // clean unreferenced transports
-                if (VERSION.SDK_INT >= VERSION_CODES.N) {
-                    transports.removeIf(reference -> reference.get() == null);
-                } else {
-                    Iterator<WeakReference<LocationListenerTransport>> it = transports.iterator();
-                    while (it.hasNext()) {
-                        if (it.next().get() == null) {
-                            it.remove();
-                        }
-                    }
-                }
-            }
-
-            transports.add(new WeakReference<>(this));
+        public LocationListenerKey getKey() {
+            return ObjectsCompat.requireNonNull(mKey);
         }
 
-        @GuardedBy("sLocationListeners")
-        public boolean unregister() {
-            LocationListenerCompat listener = mListener;
-            if (listener == null) {
-                return false;
-            }
-            mListener = null;
-
-            List<WeakReference<LocationListenerTransport>> transports =
-                    sLocationListeners.get(listener);
-            if (transports != null) {
-                // clean unreferenced transports
-                if (VERSION.SDK_INT >= VERSION_CODES.N) {
-                    transports.removeIf(reference -> reference.get() == null);
-                } else {
-                    Iterator<WeakReference<LocationListenerTransport>> it = transports.iterator();
-                    while (it.hasNext()) {
-                        if (it.next().get() == null) {
-                            it.remove();
-                        }
-                    }
-                }
-                if (transports.isEmpty()) {
-                    sLocationListeners.remove(listener);
-                }
-            }
-
-            return true;
+        public void unregister() {
+            mKey = null;
         }
 
         @Override
         public void onLocationChanged(@NonNull Location location) {
-            final LocationListenerCompat listener = mListener;
-            if (listener == null) {
+            if (mKey == null) {
                 return;
             }
 
             mExecutor.execute(() -> {
-                if (mListener != listener) {
+                LocationListenerKey key = mKey;
+                if (key == null) {
                     return;
                 }
-                listener.onLocationChanged(location);
+                key.mListener.onLocationChanged(location);
             });
         }
 
         @Override
         public void onLocationChanged(@NonNull List<Location> locations) {
-            final LocationListenerCompat listener = mListener;
-            if (listener == null) {
+            if (mKey == null) {
                 return;
             }
 
             mExecutor.execute(() -> {
-                if (mListener != listener) {
+                LocationListenerKey key = mKey;
+                if (key == null) {
                     return;
                 }
-                listener.onLocationChanged(locations);
+                key.mListener.onLocationChanged(locations);
             });
         }
 
         @Override
         public void onFlushComplete(int requestCode) {
-            final LocationListenerCompat listener = mListener;
-            if (listener == null) {
+            if (mKey == null) {
                 return;
             }
 
             mExecutor.execute(() -> {
-                if (mListener != listener) {
+                LocationListenerKey key = mKey;
+                if (key == null) {
                     return;
                 }
-                listener.onFlushComplete(requestCode);
+                key.mListener.onFlushComplete(requestCode);
             });
         }
 
         @Override
         public void onStatusChanged(String provider, int status, Bundle extras) {
-            final LocationListenerCompat listener = mListener;
-            if (listener == null) {
+            if (mKey == null) {
                 return;
             }
 
             mExecutor.execute(() -> {
-                if (mListener != listener) {
+                LocationListenerKey key = mKey;
+                if (key == null) {
                     return;
                 }
-                listener.onStatusChanged(provider, status, extras);
+                key.mListener.onStatusChanged(provider, status, extras);
             });
         }
 
         @Override
         public void onProviderEnabled(@NonNull String provider) {
-            final LocationListenerCompat listener = mListener;
-            if (listener == null) {
+            if (mKey == null) {
                 return;
             }
 
             mExecutor.execute(() -> {
-                if (mListener != listener) {
+                LocationListenerKey key = mKey;
+                if (key == null) {
                     return;
                 }
-                listener.onProviderEnabled(provider);
+                key.mListener.onProviderEnabled(provider);
             });
         }
 
         @Override
         public void onProviderDisabled(@NonNull String provider) {
-            final LocationListenerCompat listener = mListener;
-            if (listener == null) {
+            if (mKey == null) {
                 return;
             }
 
             mExecutor.execute(() -> {
-                if (mListener != listener) {
+                LocationListenerKey key = mKey;
+                if (key == null) {
                     return;
                 }
-                listener.onProviderDisabled(provider);
+                key.mListener.onProviderDisabled(provider);
             });
         }
     }
diff --git a/core/core/src/main/java/androidx/core/widget/NestedScrollView.java b/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
index ab3531c..9beab8c 100644
--- a/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
+++ b/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
@@ -2218,10 +2218,17 @@
             if (!nsvHost.isEnabled()) {
                 return false;
             }
+            int height = nsvHost.getHeight();
+            Rect rect = new Rect();
+            // Gets the visible rect on the screen except for the rotation or scale cases which
+            // might affect the result.
+            if (nsvHost.getMatrix().isIdentity() && nsvHost.getGlobalVisibleRect(rect)) {
+                height = rect.height();
+            }
             switch (action) {
                 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
                 case android.R.id.accessibilityActionScrollDown: {
-                    final int viewportHeight = nsvHost.getHeight() - nsvHost.getPaddingBottom()
+                    final int viewportHeight = height - nsvHost.getPaddingBottom()
                             - nsvHost.getPaddingTop();
                     final int targetScrollY = Math.min(nsvHost.getScrollY() + viewportHeight,
                             nsvHost.getScrollRange());
@@ -2233,7 +2240,7 @@
                 return false;
                 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
                 case android.R.id.accessibilityActionScrollUp: {
-                    final int viewportHeight = nsvHost.getHeight() - nsvHost.getPaddingBottom()
+                    final int viewportHeight = height - nsvHost.getPaddingBottom()
                             - nsvHost.getPaddingTop();
                     final int targetScrollY = Math.max(nsvHost.getScrollY() - viewportHeight, 0);
                     if (targetScrollY != nsvHost.getScrollY()) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 07a47c7..0c66aaa 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -3153,8 +3153,10 @@
 
     void dispatchOnHiddenChanged() {
         for (Fragment fragment : mFragmentStore.getActiveFragments()) {
-            fragment.mChildFragmentManager.dispatchOnHiddenChanged();
-            fragment.onHiddenChanged(fragment.isHidden());
+            if (fragment != null) {
+                fragment.onHiddenChanged(fragment.isHidden());
+                fragment.mChildFragmentManager.dispatchOnHiddenChanged();
+            }
         }
     }
 
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index ff2e7c2..093078b 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -4,6 +4,10 @@
   public final class ApplyModifiersKt {
   }
 
+  public final class BackgroundKt {
+    method public static androidx.glance.Modifier background(androidx.glance.Modifier, int day, int night);
+  }
+
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.appwidget.GlanceId> getLocalGlanceId();
@@ -130,3 +134,11 @@
 
 }
 
+package androidx.glance.appwidget.unit {
+
+  public final class ColorProviderKt {
+    method public static androidx.glance.unit.ColorProvider ColorProvider(int day, int night);
+  }
+
+}
+
diff --git a/glance/glance-appwidget/api/public_plus_experimental_current.txt b/glance/glance-appwidget/api/public_plus_experimental_current.txt
index ff2e7c2..093078b 100644
--- a/glance/glance-appwidget/api/public_plus_experimental_current.txt
+++ b/glance/glance-appwidget/api/public_plus_experimental_current.txt
@@ -4,6 +4,10 @@
   public final class ApplyModifiersKt {
   }
 
+  public final class BackgroundKt {
+    method public static androidx.glance.Modifier background(androidx.glance.Modifier, int day, int night);
+  }
+
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.appwidget.GlanceId> getLocalGlanceId();
@@ -130,3 +134,11 @@
 
 }
 
+package androidx.glance.appwidget.unit {
+
+  public final class ColorProviderKt {
+    method public static androidx.glance.unit.ColorProvider ColorProvider(int day, int night);
+  }
+
+}
+
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index ff2e7c2..093078b 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -4,6 +4,10 @@
   public final class ApplyModifiersKt {
   }
 
+  public final class BackgroundKt {
+    method public static androidx.glance.Modifier background(androidx.glance.Modifier, int day, int night);
+  }
+
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.appwidget.GlanceId> getLocalGlanceId();
@@ -130,3 +134,11 @@
 
 }
 
+package androidx.glance.appwidget.unit {
+
+  public final class ColorProviderKt {
+    method public static androidx.glance.unit.ColorProvider ColorProvider(int day, int night);
+  }
+
+}
+
diff --git a/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/GenerateRegistry.kt b/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/GenerateRegistry.kt
index 7f513eb..2e01541 100644
--- a/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/GenerateRegistry.kt
+++ b/glance/glance-appwidget/glance-layout-generator/src/main/kotlin/androidx/glance/appwidget/layoutgenerator/GenerateRegistry.kt
@@ -41,7 +41,6 @@
 ) {
     outputSourceDir.mkdirs()
     val file = FileSpec.builder(packageName, "GeneratedLayouts")
-    file.addComment(LicenseComment)
     val generatedLayouts = PropertySpec.builder(
         "generatedLayouts",
         LayoutsMap,
@@ -72,27 +71,27 @@
 
 private fun createFileInitializer(layout: File, mainViewId: String): CodeBlock = buildCodeBlock {
     val viewType = layout.nameWithoutExtension.toLayoutType()
-    ValidSize.values().forEach { width ->
-        ValidSize.values().forEach { height ->
-            addLayout(
-                resourceName = makeSimpleResourceName(layout, width, height),
-                viewType = viewType,
-                width = width,
-                height = height,
-                canResize = false,
-                mainViewId = "R.id.$mainViewId",
-                sizeViewId = null
-            )
-            addLayout(
-                resourceName = makeComplexResourceName(layout, width, height),
-                viewType = viewType,
-                width = width,
-                height = height,
-                canResize = true,
-                mainViewId = "R.id.$mainViewId",
-                sizeViewId = "R.id.sizeView"
-            )
-        }
+    forEachConfiguration(layout) { width, height, childCount ->
+        addLayout(
+            resourceName = makeSimpleResourceName(layout, width, height, childCount),
+            viewType = viewType,
+            width = width,
+            height = height,
+            canResize = false,
+            mainViewId = "R.id.$mainViewId",
+            sizeViewId = null,
+            childCount = childCount
+        )
+        addLayout(
+            resourceName = makeComplexResourceName(layout, width, height, childCount),
+            viewType = viewType,
+            width = width,
+            height = height,
+            canResize = true,
+            mainViewId = "R.id.$mainViewId",
+            sizeViewId = "R.id.sizeView",
+            childCount = childCount
+        )
     }
 }
 
@@ -103,14 +102,16 @@
     height: ValidSize,
     canResize: Boolean,
     mainViewId: String,
-    sizeViewId: String?
+    sizeViewId: String?,
+    childCount: Int
 ) {
     addStatement(
-        "%T(type = %M, width = %M, height = %M, canResize = $canResize) to ",
+        "%T(type = %M, width = %M, height = %M, canResize = $canResize, childCount = %L) to ",
         LayoutSelector,
         makeViewType(viewType),
         width.toValue(),
         height.toValue(),
+        childCount
     )
     withIndent {
         addStatement("%T(", LayoutIds)
@@ -149,11 +150,36 @@
     ValidSize.Match -> MatchValue
 }
 
-internal fun makeSimpleResourceName(file: File, width: ValidSize, height: ValidSize) =
-    "${file.nameWithoutExtension}_simple_${width.resourceName}_${height.resourceName}"
+internal fun makeSimpleResourceName(
+    file: File,
+    width: ValidSize,
+    height: ValidSize,
+    childCount: Int
+) = makeResourceName(file, width, height, childCount, isSimple = true)
 
-internal fun makeComplexResourceName(file: File, width: ValidSize, height: ValidSize) =
-    "${file.nameWithoutExtension}_complex_${width.resourceName}_${height.resourceName}"
+internal fun makeComplexResourceName(
+    file: File,
+    width: ValidSize,
+    height: ValidSize,
+    childCount: Int
+) = makeResourceName(file, width, height, childCount, isSimple = false)
+
+private fun makeResourceName(
+    file: File,
+    width: ValidSize,
+    height: ValidSize,
+    childCount: Int,
+    isSimple: Boolean,
+): String {
+    return listOfNotNull(
+        file.nameWithoutExtension,
+        if (isSimple) "simple" else "complex",
+        width.resourceName,
+        height.resourceName,
+        if (childCount > 0) "${childCount}child" else null
+    )
+        .joinToString(separator = "_")
+}
 
 fun CodeBlock.Builder.withIndent(builderAction: CodeBlock.Builder.() -> Unit): CodeBlock.Builder {
     indent()
@@ -162,19 +188,24 @@
     return this
 }
 
-val LicenseComment =
-    """
-        Copyright 2021 The Android Open Source Project
+/**
+ * The list of layout templates corresponding to collections that should have view stub children.
+ */
+private val CollectionFiles = listOf("box", "column", "row")
 
-        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
+/**
+ * Returns whether the [File] is for a collection layout that should have generated view stub
+ * children.
+ */
+internal fun File.isCollectionLayout() = nameWithoutExtension in CollectionFiles
 
-              http://www.apache.org/licenses/LICENSE-2.0
+internal fun File.allChildCounts(): List<Int> {
+    return if (isCollectionLayout()) {
+        (0..MaxChildren).toList()
+    } else {
+        listOf(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.
-    """.trimIndent()
+/** The maximum number of direct children that a collection layout can have. */
+internal const val MaxChildren = 10
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 b79fc53..ab7c834 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
@@ -78,26 +78,35 @@
     fun generateAllFiles(files: List<File>, outputResourcesDir: File): Map<File, LayoutProperties> {
         val outputLayoutDir = outputResourcesDir.resolve("layout")
         outputLayoutDir.mkdirs()
-        return files.associate {
-            it to generateForFile(it, outputLayoutDir)
-        }
+        return files.associateWith { generateForFile(it, outputLayoutDir) }
     }
 
     private fun generateForFile(file: File, outputLayoutDir: File): LayoutProperties {
         val document = parseLayoutTemplate(file)
-        ValidSize.values().forEach { width ->
-            ValidSize.values().forEach { height ->
-                val simpleLayout = generateSimpleLayout(document, width, height)
-                writeGeneratedLayout(
-                    simpleLayout,
-                    outputLayoutDir.resolve("${makeSimpleResourceName(file, width, height)}.xml")
+        forEachConfiguration(file) { width, height, childCount ->
+            writeGeneratedLayout(
+                generateSimpleLayout(document, width, height, childCount),
+                outputLayoutDir.resolve(
+                    makeSimpleResourceName(
+                        file,
+                        width,
+                        height,
+                        childCount
+                    ) + ".xml"
                 )
-                val complexLayout = generateComplexLayout(document, width, height)
-                writeGeneratedLayout(
-                    complexLayout,
-                    outputLayoutDir.resolve("${makeComplexResourceName(file, width, height)}.xml")
+            )
+            writeGeneratedLayout(
+                generateComplexLayout(document, width, height, childCount),
+                outputLayoutDir.resolve(
+                    makeComplexResourceName(
+                        file,
+                        width,
+                        height,
+                        childCount
+                    ) + ".xml"
+
                 )
-            }
+            )
         }
         return LayoutProperties(mainViewId = extractMainViewId(document))
     }
@@ -111,11 +120,11 @@
     fun generateSimpleLayout(
         document: Document,
         width: ValidSize,
-        height: ValidSize
+        height: ValidSize,
+        childCount: Int
     ): Document {
         val generated = documentBuilder.newDocument()
         val root = generated.importNode(document.documentElement, true)
-        generated.appendChild(generated.createComment(LicenseComment))
         generated.appendChild(root)
         root.attributes.apply {
             setNamedItem(generated.androidNamespace)
@@ -129,6 +138,7 @@
             }
             setNamedItemNS(generated.androidLayoutDirection("locale"))
         }
+        generated.appendViewStubs(root, childCount)
         return generated
     }
 
@@ -179,11 +189,11 @@
     fun generateComplexLayout(
         document: Document,
         width: ValidSize,
-        height: ValidSize
+        height: ValidSize,
+        childCount: Int
     ): Document {
         val generated = documentBuilder.newDocument()
         val root = generated.createElement("RelativeLayout")
-        generated.appendChild(generated.createComment(LicenseComment))
         generated.appendChild(root)
         root.attributes.apply {
             setNamedItemNS(generated.androidId("@id/relativeLayout"))
@@ -228,10 +238,25 @@
             }
             setNamedItemNS(generated.androidLayoutDirection("locale"))
         }
+        generated.appendViewStubs(mainNode, childCount)
         return generated
     }
 }
 
+internal fun Document.appendViewStubs(node: Node, childCount: Int) {
+    repeat(childCount) { node.appendChild(createViewStub(index = it)) }
+}
+
+internal fun Document.createViewStub(index: Int): Node {
+    val stub = createElement("ViewStub")
+    stub.attributes.apply {
+        setNamedItemNS(androidId("@id/stub$index"))
+        setNamedItemNS(androidWidth(ValidSize.Wrap))
+        setNamedItemNS(androidHeight(ValidSize.Wrap))
+    }
+    return stub
+}
+
 internal data class LayoutProperties(
     val mainViewId: String
 )
@@ -282,3 +307,16 @@
     ValidSize.Wrap, ValidSize.Fixed -> ValidSize.Wrap
     ValidSize.Match, ValidSize.Expand -> ValidSize.Match
 }
+
+internal inline fun forEachConfiguration(
+    file: File,
+    function: (width: ValidSize, height: ValidSize, childCount: Int) -> Unit
+) {
+    ValidSize.values().forEach { width ->
+        ValidSize.values().forEach { height ->
+            file.allChildCounts().forEach { childCount ->
+                function(width, height, childCount)
+            }
+        }
+    }
+}
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml b/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
index aad66df..adaaef7 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
@@ -62,27 +62,15 @@
         </receiver>
 
         <receiver
-            android:name="androidx.glance.appwidget.demos.CheckBoxAppWidgetReceiver"
-            android:label="@string/check_box_widget_name"
+            android:name="androidx.glance.appwidget.demos.CompoundButtonAppWidgetReceiver"
+            android:label="@string/compound_button_widget_name"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
             </intent-filter>
             <meta-data
                 android:name="android.appwidget.provider"
-                android:resource="@xml/check_box_app_widget_info" />
-        </receiver>
-
-        <receiver
-            android:name="androidx.glance.appwidget.demos.SwitchAppWidgetReceiver"
-            android:label="@string/switch_widget_name"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-            </intent-filter>
-            <meta-data
-                android:name="android.appwidget.provider"
-                android:resource="@xml/check_box_app_widget_info" />
+                android:resource="@xml/compound_button_app_widget_info" />
         </receiver>
     </application>
 </manifest>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CheckBoxAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CheckBoxAppWidget.kt
deleted file mode 100644
index 5e710d3..0000000
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CheckBoxAppWidget.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.demos
-
-import androidx.compose.runtime.Composable
-import androidx.glance.Modifier
-import androidx.glance.appwidget.GlanceAppWidget
-import androidx.glance.appwidget.GlanceAppWidgetReceiver
-import androidx.glance.appwidget.layout.CheckBox
-import androidx.glance.background
-import androidx.glance.layout.Column
-import androidx.glance.text.FontStyle
-import androidx.glance.text.FontWeight
-import androidx.glance.text.TextStyle
-import androidx.glance.unit.Color
-import androidx.glance.unit.sp
-
-class CheckBoxAppWidget : GlanceAppWidget() {
-    @Composable
-    override fun Content() {
-        Column(modifier = Modifier.background(Color.LightGray)) {
-            CheckBox(checked = true, text = "Checkbox 1")
-
-            CheckBox(
-                checked = false,
-                text = "Checkbox 2",
-                textStyle = TextStyle(
-                    fontSize = 16.sp,
-                    fontWeight = FontWeight.Bold,
-                    fontStyle = FontStyle.Italic
-                )
-            )
-        }
-    }
-}
-
-class CheckBoxAppWidgetReceiver : GlanceAppWidgetReceiver() {
-    override val glanceAppWidget: GlanceAppWidget by lazy { CheckBoxAppWidget() }
-}
\ 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
new file mode 100644
index 0000000..51d4762
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.demos
+
+import androidx.compose.runtime.Composable
+import androidx.glance.LocalSize
+import androidx.glance.Modifier
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetReceiver
+import androidx.glance.appwidget.SizeMode
+import androidx.glance.appwidget.layout.CheckBox
+import androidx.glance.appwidget.layout.Switch
+import androidx.glance.background
+import androidx.glance.layout.Alignment
+import androidx.glance.layout.Column
+import androidx.glance.layout.fillMaxSize
+import androidx.glance.layout.fillMaxWidth
+import androidx.glance.layout.padding
+import androidx.glance.text.FontStyle
+import androidx.glance.text.FontWeight
+import androidx.glance.text.TextStyle
+import androidx.glance.unit.Color
+import androidx.glance.unit.dp
+import androidx.glance.unit.sp
+
+class CompoundButtonAppWidget : GlanceAppWidget() {
+
+    override val sizeMode: SizeMode = SizeMode.Exact
+
+    @Composable
+    override fun Content() {
+        val size = LocalSize.current
+        val toggled = size.width >= 100.dp
+        Column(
+            modifier = Modifier.fillMaxSize().background(Color.LightGray).padding(8.dp),
+            verticalAlignment = Alignment.Vertical.CenterVertically,
+            horizontalAlignment = Alignment.Horizontal.CenterHorizontally
+        ) {
+            val textStyle = TextStyle(
+                fontSize = 16.sp,
+                fontWeight = FontWeight.Bold,
+                fontStyle = FontStyle.Italic
+            )
+            val fillModifier = Modifier.fillMaxWidth()
+
+            CheckBox(checked = toggled, text = "Checkbox 1")
+            CheckBox(
+                checked = !toggled,
+                text = "Checkbox 2",
+                textStyle = textStyle,
+                modifier = fillModifier
+            )
+            Switch(checked = toggled, text = "Switch 1")
+            Switch(
+                checked = !toggled,
+                text = "Switch 2",
+                textStyle = textStyle,
+                modifier = fillModifier
+            )
+        }
+    }
+}
+
+class CompoundButtonAppWidgetReceiver : GlanceAppWidgetReceiver() {
+    override val glanceAppWidget = CompoundButtonAppWidget()
+}
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ExactAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ExactAppWidget.kt
index 806aa7d..c323a589 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ExactAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ExactAppWidget.kt
@@ -23,7 +23,7 @@
 import androidx.glance.appwidget.GlanceAppWidget
 import androidx.glance.appwidget.GlanceAppWidgetReceiver
 import androidx.glance.appwidget.SizeMode
-import androidx.glance.background
+import androidx.glance.appwidget.background
 import androidx.glance.layout.Column
 import androidx.glance.layout.Text
 import androidx.glance.layout.fillMaxSize
@@ -41,13 +41,18 @@
     @Composable
     override fun Content() {
         val context = LocalContext.current
-        Column(modifier = Modifier.fillMaxSize().background(Color.LightGray).padding(8.dp)) {
+        Column(
+            modifier = Modifier
+                .fillMaxSize()
+                .background(day = Color.LightGray, night = Color.DarkGray)
+                .padding(8.dp)
+        ) {
             Text(
                 context.getString(R.string.exact_widget_title),
                 style = TextStyle(
                     fontWeight = FontWeight.Bold,
                     textDecoration = TextDecoration.Underline
-                )
+                ),
             )
             val size = LocalSize.current
             val dec = DecimalFormat("#.##")
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResizingAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResizingAppWidget.kt
index 4eb3ab3..a8e55e5 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResizingAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResizingAppWidget.kt
@@ -88,5 +88,5 @@
 }
 
 class ResizingAppWidgetReceiver : GlanceAppWidgetReceiver() {
-    override val glanceAppWidget: GlanceAppWidget by lazy { ResizingAppWidget() }
+    override val glanceAppWidget: GlanceAppWidget = ResizingAppWidget()
 }
\ No newline at end of file
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResponsiveAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResponsiveAppWidget.kt
index fb4a4db..3cfee01 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResponsiveAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ResponsiveAppWidget.kt
@@ -33,7 +33,6 @@
 import androidx.glance.text.FontWeight
 import androidx.glance.text.TextDecoration
 import androidx.glance.text.TextStyle
-import androidx.glance.unit.Color
 import androidx.glance.unit.DpSize
 import androidx.glance.unit.dp
 import androidx.glance.unit.sp
@@ -50,7 +49,11 @@
     override fun Content() {
         val size = LocalSize.current
         val context = LocalContext.current
-        Column(modifier = Modifier.padding(8.dp).background(Color.LightGray)) {
+        Column(
+            modifier = Modifier
+                .padding(8.dp)
+                .background(R.color.responsive_widget_background)
+        ) {
             val content = if (size.width < 100.dp) {
                 "${size.width.value}dp x ${size.height.value}dp"
             } else {
@@ -72,5 +75,5 @@
 }
 
 class ResponsiveAppWidgetReceiver : GlanceAppWidgetReceiver() {
-    override val glanceAppWidget: GlanceAppWidget by lazy { ResponsiveAppWidget() }
+    override val glanceAppWidget: GlanceAppWidget = ResponsiveAppWidget()
 }
\ No newline at end of file
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/SwitchAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/SwitchAppWidget.kt
deleted file mode 100644
index 09969c1..0000000
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/SwitchAppWidget.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.demos
-
-import androidx.compose.runtime.Composable
-import androidx.glance.LocalSize
-import androidx.glance.Modifier
-import androidx.glance.appwidget.GlanceAppWidget
-import androidx.glance.appwidget.GlanceAppWidgetReceiver
-import androidx.glance.appwidget.SizeMode
-import androidx.glance.appwidget.layout.LazyColumn
-import androidx.glance.appwidget.layout.Switch
-import androidx.glance.background
-import androidx.glance.text.FontStyle
-import androidx.glance.text.FontWeight
-import androidx.glance.text.TextStyle
-import androidx.glance.unit.Color
-import androidx.glance.unit.dp
-import androidx.glance.unit.sp
-
-class SwitchAppWidget : GlanceAppWidget() {
-
-    override val sizeMode: SizeMode = SizeMode.Exact
-
-    @Composable
-    override fun Content() {
-        val size = LocalSize.current
-        LazyColumn(modifier = Modifier.background(Color.LightGray)) {
-            item {
-                Switch(checked = size.width >= 100.dp, text = "Switch 1")
-            }
-
-            item {
-                Switch(
-                    checked = size.width < 100.dp,
-                    text = "Switch 2",
-                    textStyle = TextStyle(
-                        fontSize = 16.sp,
-                        fontWeight = FontWeight.Bold,
-                        fontStyle = FontStyle.Italic
-                    )
-                )
-            }
-        }
-    }
-}
-
-class SwitchAppWidgetReceiver : GlanceAppWidgetReceiver() {
-    override val glanceAppWidget = SwitchAppWidget()
-}
\ No newline at end of file
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/check_box_preview_image.png b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/compound_button_preview_image.png
similarity index 100%
rename from glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/check_box_preview_image.png
rename to glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/compound_button_preview_image.png
Binary files differ
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/layout/exact_preview_layout.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/layout/exact_preview_layout.xml
index fb402a3..3214aab 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/res/layout/exact_preview_layout.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/layout/exact_preview_layout.xml
@@ -20,7 +20,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:padding="8dp"
-    android:background="@color/light_gray"
+    android:background="@color/exact_widget_background"
     android:gravity="center_vertical">
     <TextView
         android:layout_width="wrap_content"
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/layout/responsive_preview_layout.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/layout/responsive_preview_layout.xml
index 05fc976..81a4a56 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/res/layout/responsive_preview_layout.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/layout/responsive_preview_layout.xml
@@ -20,7 +20,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:padding="8dp"
-    android:background="@color/light_gray"
+    android:background="@color/responsive_widget_background"
     android:gravity="center_vertical">
     <TextView
         android:layout_width="wrap_content"
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/values-night/colors.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..ffb1de0
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/values-night/colors.xml
@@ -0,0 +1,20 @@
+<?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>
+    <color name="responsive_widget_background">#FF444444</color>
+    <color name="exact_widget_background">@color/dark_gray</color>
+</resources>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/colors.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/colors.xml
index 2924c7b..40dea12 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/colors.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/colors.xml
@@ -16,4 +16,7 @@
 
 <resources>
     <color name="light_gray">#FFCCCCCC</color>
+    <color name="dark_gray">#FF444444</color>
+    <color name="responsive_widget_background">#FFCCCCCC</color>
+    <color name="exact_widget_background">@color/light_gray</color>
 </resources>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
index 6ce819f..3054240 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
@@ -16,12 +16,12 @@
 
 <resources>
     <string name="demos_label">Glance AppWidget Demos</string>
-    <!-- Name of the app widget with a responsive layout. -->
+
+    <!-- Name of the app widgets -->
     <string name="responsive_widget_name">Responsive Widget</string>
     <string name="exact_widget_name">Exact Widget</string>
     <string name="responsive_widget_title"><u>Responsive layout</u></string>
     <string name="exact_widget_title"><u>Exact layout</u></string>
     <string name="resizing_widget_name">Resizing Widget</string>
-    <string name="check_box_widget_name">CheckBox Widget</string>
-    <string name="switch_widget_name">Switch Widget</string>
+    <string name="compound_button_widget_name">CheckBox Widget</string>
 </resources>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/xml/check_box_app_widget_info.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/xml/compound_button_app_widget_info.xml
similarity index 93%
rename from glance/glance-appwidget/integration-tests/demos/src/main/res/xml/check_box_app_widget_info.xml
rename to glance/glance-appwidget/integration-tests/demos/src/main/res/xml/compound_button_app_widget_info.xml
index 35335ff..276f129 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/res/xml/check_box_app_widget_info.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/xml/compound_button_app_widget_info.xml
@@ -20,7 +20,7 @@
     android:minResizeWidth="40dp"
     android:minResizeHeight="40dp"
     android:initialLayout="@layout/empty_layout"
-    android:previewImage="@drawable/check_box_preview_image"
+    android:previewImage="@drawable/compound_button_preview_image"
     android:resizeMode="horizontal|vertical"
     android:widgetCategory="home_screen">
 </appwidget-provider>
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AndroidTestUtils.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AndroidTestUtils.kt
index 6fee7a2..2eee9fd 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AndroidTestUtils.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AndroidTestUtils.kt
@@ -17,6 +17,7 @@
 package androidx.glance.appwidget
 
 import android.appwidget.AppWidgetManager
+import android.content.Context
 import android.os.Build
 import android.os.Bundle
 import android.view.View
@@ -26,6 +27,9 @@
 import androidx.glance.unit.max
 import androidx.glance.unit.min
 import androidx.glance.unit.toSizeF
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.FileInputStream
 
 inline fun <reified T : View> View.findChild(noinline pred: (T) -> Boolean) =
     findChild(pred, T::class.java)
@@ -69,3 +73,14 @@
         }
     }
 }
+
+/** Run a command and retrieve the output as a string. */
+internal fun runShellCommand(command: String): String {
+    return InstrumentationRegistry.getInstrumentation()
+        .uiAutomation
+        .executeShellCommand(command)
+        .use { FileInputStream(it.fileDescriptor).reader().readText() }
+}
+
+internal val context: Context
+    get() = ApplicationProvider.getApplicationContext()
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
index a4c138b..fbb5e79 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
@@ -21,6 +21,7 @@
 import androidx.glance.background
 import androidx.glance.appwidget.layout.CheckBox
 import androidx.glance.appwidget.layout.Switch
+import androidx.glance.appwidget.test.R
 import androidx.glance.layout.Box
 import androidx.glance.layout.Column
 import androidx.glance.layout.Row
@@ -53,6 +54,7 @@
     @JvmField
     val mRule: TestRule = RuleChain.outerRule(mHostRule).around(mScreenshotRule)
         .around(WithRtlRule)
+        .around(WithNightModeRule)
 
     @Test
     fun createSimpleAppWidget() {
@@ -171,38 +173,23 @@
     }
 
     @Test
-    fun checkBackgroundColor() {
-        TestGlanceAppWidget.uiDefinition = {
-            Column(modifier = Modifier.background(Color.White)) {
-                Text(
-                    "100x50 and cyan",
-                    modifier = Modifier.width(100.dp).height(50.dp).background(Color.Cyan)
-                )
-                Text(
-                    "Transparent background",
-                    modifier = Modifier.height(50.dp).background(Color.Transparent)
-                )
-                Text(
-                    "wrapx50 and red",
-                    modifier = Modifier.height(50.dp).background(Color.Red)
-                )
-                Text("Below this should be 4 color boxes")
-                Row(modifier = Modifier.padding(8.dp)) {
-                    val colors = listOf(Color.Black, Color.Red, Color.Green, Color.Blue)
-                    repeat(4) {
-                        Box(
-                            modifier = Modifier.width(32.dp).height(32.dp).background(colors[it])
-                        ) {}
-                        Box(modifier = Modifier.width(8.dp).height(1.dp)) {}
-                    }
-                }
-            }
-        }
+    fun checkBackgroundColor_light() {
+        TestGlanceAppWidget.uiDefinition = { BackgroundTest() }
 
         mHostRule.startHost()
 
         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "backgroundColor")
     }
+
+    @Test
+    @WithNightMode
+    fun checkBackgroundColor_dark() {
+        TestGlanceAppWidget.uiDefinition = { BackgroundTest() }
+
+        mHostRule.startHost()
+
+        mScreenshotRule.checkScreenshot(mHostRule.mHostView, "backgroundColor_dark")
+    }
 }
 
 @Composable
@@ -256,3 +243,38 @@
         )
     }
 }
+
+@Composable
+private fun BackgroundTest() {
+    Column(modifier = Modifier.background(R.color.background_color)) {
+        Text(
+            "100x50 and cyan",
+            modifier = Modifier.width(100.dp).height(50.dp).background(Color.Cyan)
+        )
+        Text(
+            "Transparent background",
+            modifier = Modifier.height(50.dp).background(Color.Transparent)
+        )
+        Text(
+            "wrapx30 and red (light), yellow (dark)",
+            modifier = Modifier.height(30.dp).background(day = Color.Red, night = Color.Yellow)
+        )
+        Text("Below this should be 4 color boxes")
+        Row(modifier = Modifier.padding(8.dp)) {
+            Box(
+                modifier =
+                Modifier
+                    .width(32.dp)
+                    .height(32.dp)
+                    .background(day = Color.Black, night = Color.White)
+            ) {}
+            val colors = listOf(Color.Red, Color.Green, Color.Blue)
+            repeat(3) {
+                Box(modifier = Modifier.width(8.dp).height(1.dp)) {}
+                Box(
+                    modifier = Modifier.width(32.dp).height(32.dp).background(colors[it])
+                ) {}
+            }
+        }
+    }
+}
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/WithNightMode.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/WithNightMode.kt
new file mode 100644
index 0000000..45492af
--- /dev/null
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/WithNightMode.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.appwidget
+
+import android.content.res.Configuration
+import android.os.Build
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import java.lang.reflect.AnnotatedElement
+import java.lang.reflect.Method
+
+/**
+ * Annotation for specifying a per-test or per-method override of the device night mode
+ */
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+internal annotation class WithNightMode(val value: Boolean = true)
+
+/** [TestRule] for using [WithNightMode] on a test class or method. */
+internal object WithNightModeRule : TestRule {
+
+    override fun apply(base: Statement, description: Description) =
+        object : Statement() {
+            override fun evaluate() {
+                if (Build.VERSION.SDK_INT < 29) {
+                    // Night mode is only usable on API 29+.
+                    base.evaluate()
+                    return
+                }
+
+                val isTestNightMode =
+                    description.testMethod.isNightMode ?: description.testClass.isNightMode ?: false
+                val isDeviceNightMode =
+                    context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+                        Configuration.UI_MODE_NIGHT_YES
+                var previousNightMode: String? = null
+                if (isTestNightMode != isDeviceNightMode) {
+                    // The night mode status is of the form "Night mode: <mode>"
+                    previousNightMode = runShellCommand("cmd uimode night").substring(12)
+                    val newNightMode = if (isTestNightMode) "yes" else "no"
+                    runShellCommand("cmd uimode night $newNightMode")
+                }
+                try {
+                    base.evaluate()
+                } finally {
+                    if (previousNightMode != null) {
+                        runShellCommand("cmd uimode night $previousNightMode")
+                    }
+                }
+            }
+        }
+
+    private val AnnotatedElement.isNightMode: Boolean?
+        get() = getAnnotation(WithNightMode::class.java)?.value
+}
+
+private val Description.testMethod: Method
+    get() = testClass.getMethod(methodName)
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/androidAndroidTest/res/values-night/colors.xml b/glance/glance-appwidget/src/androidAndroidTest/res/values-night/colors.xml
new file mode 100644
index 0000000..ad2e176
--- /dev/null
+++ b/glance/glance-appwidget/src/androidAndroidTest/res/values-night/colors.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>
+    <color name="background_color">@android:color/black</color>
+</resources>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/androidAndroidTest/res/values/colors.xml b/glance/glance-appwidget/src/androidAndroidTest/res/values/colors.xml
new file mode 100644
index 0000000..2506f18
--- /dev/null
+++ b/glance/glance-appwidget/src/androidAndroidTest/res/values/colors.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>
+    <color name="background_color">@android:color/white</color>
+</resources>
\ No newline at end of file
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 5209c78..ff6afb9 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
@@ -31,6 +31,7 @@
 import androidx.core.widget.setTextViewHeight
 import androidx.core.widget.setTextViewWidth
 import androidx.core.widget.setViewBackgroundColor
+import androidx.core.widget.setViewBackgroundColorResource
 import androidx.glance.BackgroundModifier
 import androidx.glance.Modifier
 import androidx.glance.action.Action
@@ -40,12 +41,15 @@
 import androidx.glance.action.LaunchActivityComponentAction
 import androidx.glance.action.UpdateContentAction
 import androidx.glance.appwidget.action.LaunchActivityIntentAction
+import androidx.glance.appwidget.unit.DayNightColorProvider
 import androidx.glance.layout.Dimension
 import androidx.glance.layout.HeightModifier
 import androidx.glance.layout.PaddingModifier
 import androidx.glance.layout.WidthModifier
 import androidx.glance.layout.collectPaddingInDp
 import androidx.glance.unit.Color
+import androidx.glance.unit.FixedColorProvider
+import androidx.glance.unit.ResourceColorProvider
 import androidx.glance.unit.dp
 import kotlin.math.roundToInt
 
@@ -74,6 +78,7 @@
             is BackgroundModifier -> applyBackgroundModifier(
                 rv,
                 modifier,
+                context,
                 layoutDef
             )
             is PaddingModifier -> {
@@ -192,9 +197,26 @@
 private fun applyBackgroundModifier(
     rv: RemoteViews,
     modifier: BackgroundModifier,
+    context: Context,
     layoutDef: LayoutIds
 ) {
-    rv.setViewBackgroundColor(layoutDef.mainViewId, modifier.color.toArgb())
+    val viewId = layoutDef.mainViewId
+    when (val colorProvider = modifier.colorProvider) {
+        is FixedColorProvider -> rv.setViewBackgroundColor(viewId, colorProvider.color.toArgb())
+        is ResourceColorProvider -> rv.setViewBackgroundColorResource(viewId, colorProvider.resId)
+        is DayNightColorProvider -> {
+            if (Build.VERSION.SDK_INT >= 31) {
+                rv.setViewBackgroundColor(
+                    viewId,
+                    colorProvider.day.toArgb(),
+                    colorProvider.night.toArgb()
+                )
+            } else {
+                rv.setViewBackgroundColor(viewId, colorProvider.resolve(context).toArgb())
+            }
+        }
+        else -> Log.w(GlanceAppWidgetTag, "Unexpected background color modifier: $colorProvider")
+    }
 }
 
 // TODO(b/202150620): Use the shared Compose utility when we use the same Color class.
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/Background.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/Background.kt
new file mode 100644
index 0000000..0553921
--- /dev/null
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/Background.kt
@@ -0,0 +1,30 @@
+/*
+ * 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
+
+import androidx.glance.Modifier
+import androidx.glance.appwidget.unit.ColorProvider
+import androidx.glance.background
+import androidx.glance.unit.Color
+
+/**
+ * Apply a background color to the element this modifier is attached to. This will cause the
+ * element to paint the specified [Color] as its background, choosing [day] or [night]
+ * depending on the device configuration, which will fill the bounds of the element.
+ */
+public fun Modifier.background(day: Color, night: Color): Modifier =
+    background(ColorProvider(day, night))
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutIds.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutIds.kt
index 72d9d2e..9e9539e 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutIds.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutIds.kt
@@ -53,6 +53,7 @@
     val width: Size,
     val height: Size,
     val canResize: Boolean,
+    val childCount: Int
 ) {
 
     internal enum class Size {
@@ -101,8 +102,16 @@
     val width = widthMod.resolveDimension(context).toSpecSize()
     val height = heightMod.resolveDimension(context).toSpecSize()
     val needResize = width == LayoutSelector.Size.Fixed || height == LayoutSelector.Size.Fixed
-    return generatedLayouts[LayoutSelector(type, width, height, needResize)]
-        ?: (if (!needResize) generatedLayouts[LayoutSelector(type, width, height, true)] else null)
+    // TODO(b/202868171): Add the real child count.
+    return generatedLayouts[LayoutSelector(type, width, height, needResize, childCount = 0)]
+        ?: (if (!needResize) generatedLayouts[LayoutSelector(
+            type,
+            width,
+            height,
+            true,
+            // TODO(b/202868171): Add the real child count.
+            childCount = 0
+        )] else null)
         ?: throw IllegalArgumentException(
             "Could not find layout for $type, width=$width, height=$height, canResize=$needResize"
         )
@@ -139,7 +148,14 @@
     val heightMod = modifier.findModifier<HeightModifier>()?.height ?: Dimension.Wrap
     val width = widthMod.toSpecSize()
     val height = heightMod.toSpecSize()
-    return generatedLayouts[LayoutSelector(type, width, height, canResize = false)]
+    return generatedLayouts[LayoutSelector(
+        type,
+        width,
+        height,
+        canResize = false,
+        // TODO(b/202868171): Add the real child count.
+        childCount = 0
+    )]
         ?: throw IllegalArgumentException(
             "Could not find layout for $type, width=$width, height=$height, canResize=false"
         )
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/unit/ColorProvider.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/unit/ColorProvider.kt
new file mode 100644
index 0000000..eaa02d5
--- /dev/null
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/unit/ColorProvider.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.unit
+
+import android.content.Context
+import android.content.res.Configuration
+import androidx.glance.unit.Color
+import androidx.glance.unit.ColorProvider
+
+/**
+ * Returns a [ColorProvider] that provides [day] when night mode is off, and [night] when night
+ * mode is on.
+ */
+public fun ColorProvider(day: Color, night: Color): ColorProvider {
+    return DayNightColorProvider(day, night)
+}
+
+internal class DayNightColorProvider(val day: Color, val night: Color) : ColorProvider {
+    fun resolve(context: Context) = if (context.isNightMode) night else day
+}
+
+private val Context.isNightMode: Boolean
+    get() =
+        resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+        Configuration.UI_MODE_NIGHT_YES
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/androidMain/layoutTemplates/check_box_backport.xml b/glance/glance-appwidget/src/androidMain/layoutTemplates/check_box_backport.xml
index 1756ca7..41c6966 100644
--- a/glance/glance-appwidget/src/androidMain/layoutTemplates/check_box_backport.xml
+++ b/glance/glance-appwidget/src/androidMain/layoutTemplates/check_box_backport.xml
@@ -17,7 +17,7 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@id/glanceView"
     android:orientation="horizontal"
-    android:gravity="center">
+    android:gravity="center_vertical">
     <!-- Note: A separate ImageView is used for the icon rather than using the drawable of the
      TextView as TextView.setEnabled is only remotable since API 24, whereas View.setEnabled is
      remotable on all APIs. -->
diff --git a/glance/glance-appwidget/src/androidMain/res/values/ids.xml b/glance/glance-appwidget/src/androidMain/res/values/ids.xml
index f6750d1..2b3520d 100644
--- a/glance/glance-appwidget/src/androidMain/res/values/ids.xml
+++ b/glance/glance-appwidget/src/androidMain/res/values/ids.xml
@@ -26,4 +26,15 @@
     <id name="switchText"/>
     <id name="switchTrack"/>
     <id name="switchThumb"/>
+
+    <id name="stub0"/>
+    <id name="stub1"/>
+    <id name="stub2"/>
+    <id name="stub3"/>
+    <id name="stub4"/>
+    <id name="stub5"/>
+    <id name="stub6"/>
+    <id name="stub7"/>
+    <id name="stub8"/>
+    <id name="stub9"/>
 </resources>
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
index 2253063..9ba3bcd 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
@@ -19,7 +19,7 @@
 import android.annotation.TargetApi
 import android.app.Activity
 import android.content.Context
-import android.graphics.drawable.ColorDrawable
+import android.content.res.Configuration
 import android.os.Build
 import android.text.SpannedString
 import android.text.style.StrikethroughSpan
@@ -37,6 +37,7 @@
 import androidx.core.view.children
 import androidx.glance.Modifier
 import androidx.glance.action.actionLaunchActivity
+import androidx.glance.appwidget.ViewSubject.Companion.assertThat
 import androidx.glance.appwidget.layout.AndroidRemoteViews
 import androidx.glance.appwidget.layout.CheckBox
 import androidx.glance.appwidget.layout.LazyColumn
@@ -77,6 +78,8 @@
 
     private lateinit var fakeCoroutineScope: TestCoroutineScope
     private val context = ApplicationProvider.getApplicationContext<Context>()
+    private val lightContext = configurationContext { uiMode = Configuration.UI_MODE_NIGHT_NO }
+    private val darkContext = configurationContext { uiMode = Configuration.UI_MODE_NIGHT_YES }
     private val displayMetrics = context.resources.displayMetrics
 
     @Before
@@ -718,9 +721,8 @@
         }
 
         val view = context.applyRemoteViews(rv)
-        val background = view.background
-        assertIs<ColorDrawable>(background)
-        assertThat(background.color).isEqualTo(android.graphics.Color.RED)
+
+        assertThat(view).hasBackgroundColor(android.graphics.Color.RED)
     }
 
     @Test
@@ -730,9 +732,8 @@
         }
 
         val view = context.applyRemoteViews(rv)
-        val background = view.background
-        assertIs<ColorDrawable>(background)
-        assertThat(background.color).isEqualTo(android.graphics.Color.argb(255, 102, 128, 153))
+
+        assertThat(view).hasBackgroundColor(android.graphics.Color.argb(255, 102, 128, 153))
     }
 
     @Test
@@ -742,9 +743,43 @@
         }
 
         val view = context.applyRemoteViews(rv)
-        val background = view.background
-        assertIs<ColorDrawable>(background)
-        assertThat(background.color).isEqualTo(android.graphics.Color.TRANSPARENT)
+
+        assertThat(view).hasBackgroundColor(android.graphics.Color.TRANSPARENT)
+    }
+
+    @Config(minSdk = 29)
+    @Test
+    fun canTranslateBackground_resId() = fakeCoroutineScope.runBlockingTest {
+        val rv = runAndTranslate {
+            Box(modifier = Modifier.background(R.color.my_color)) {}
+        }
+
+        assertThat(lightContext.applyRemoteViews(rv)).hasBackgroundColor("#EEEEEE")
+        assertThat(darkContext.applyRemoteViews(rv)).hasBackgroundColor("#111111")
+    }
+
+    @Config(sdk = [30])
+    @Test
+    fun canTranslateBackground_dayNight_light() = fakeCoroutineScope.runBlockingTest {
+        val rv = runAndTranslate(context = lightContext) {
+            Box(modifier = Modifier.background(day = Color.Red, night = Color.Blue)) {}
+        }
+
+        val view = lightContext.applyRemoteViews(rv)
+
+        assertThat(view).hasBackgroundColor(android.graphics.Color.RED)
+    }
+
+    @Config(sdk = [30])
+    @Test
+    fun canTranslateBackground_dayNight_dark() = fakeCoroutineScope.runBlockingTest {
+        val rv = runAndTranslate(context = darkContext) {
+            Box(modifier = Modifier.background(day = Color.Red, night = Color.Blue)) {}
+        }
+
+        val view = darkContext.applyRemoteViews(rv)
+
+        assertThat(view).hasBackgroundColor(android.graphics.Color.BLUE)
     }
 
     // Check there is a single span, that it's of the correct type and passes the [check].
@@ -775,5 +810,11 @@
         return translateComposition(context, appWidgetId, root)
     }
 
+    private fun configurationContext(modifier: Configuration.() -> Unit): Context {
+        val configuration = Configuration()
+        modifier(configuration)
+        return context.createConfigurationContext(configuration)
+    }
+
     private fun Dp.toPixels() = toPixels(displayMetrics)
 }
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/ViewSubject.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/ViewSubject.kt
new file mode 100644
index 0000000..36ca5c6
--- /dev/null
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/ViewSubject.kt
@@ -0,0 +1,52 @@
+/*
+ * 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
+
+import android.graphics.drawable.ColorDrawable
+import android.view.View
+import androidx.annotation.ColorInt
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth.assertAbout
+
+internal class ViewSubject<V : View>(
+    metaData: FailureMetadata,
+    private val actual: V?
+) : Subject(metaData, actual) {
+    fun hasBackgroundColor(@ColorInt color: Int) {
+        isNotNull()
+        actual!!
+        check("getBackground()").that(actual.background).isInstanceOf(ColorDrawable::class.java)
+        val background = actual.background as ColorDrawable
+        // Comparing the hex string representation is equivalent to comparing the int, and the
+        // error message is a lot more readable with the hex string if this fails.
+        check("getBackground().getColor()")
+            .that(Integer.toHexString(background.color))
+            .isEqualTo(Integer.toHexString(color))
+    }
+
+    fun hasBackgroundColor(hexString: String) =
+        hasBackgroundColor(android.graphics.Color.parseColor(hexString))
+
+    companion object {
+        internal fun <V : View> views(): Factory<ViewSubject<V>, V> {
+            return Factory<ViewSubject<V>, V> { metadata, actual -> ViewSubject(metadata, actual) }
+        }
+
+        internal fun <V : View> assertThat(view: V) = assertAbout(views()).that(view)
+    }
+}
diff --git a/glance/glance-appwidget/src/test/res/values-night/colors.xml b/glance/glance-appwidget/src/test/res/values-night/colors.xml
new file mode 100644
index 0000000..2f5eb8d
--- /dev/null
+++ b/glance/glance-appwidget/src/test/res/values-night/colors.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>
+    <color name="my_color">#111111</color>
+</resources>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/test/res/values/colors.xml b/glance/glance-appwidget/src/test/res/values/colors.xml
new file mode 100644
index 0000000..ad6d560
--- /dev/null
+++ b/glance/glance-appwidget/src/test/res/values/colors.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>
+    <color name="my_color">#EEEEEE</color>
+</resources>
\ 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 c8ff6de..8142f15 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
@@ -44,7 +44,11 @@
 import androidx.glance.text.FontWeight
 import androidx.glance.text.TextDecoration
 import androidx.glance.text.TextStyle
+import androidx.glance.unit.ColorProvider
+import androidx.glance.unit.FixedColorProvider
+import androidx.glance.unit.ResourceColorProvider
 import androidx.glance.unit.dp
+import androidx.glance.unit.resolve
 import androidx.glance.wear.layout.AnchorType
 import androidx.glance.wear.layout.CurvedTextStyle
 import androidx.glance.wear.layout.EmittableAndroidLayoutElement
@@ -104,11 +108,17 @@
         .setRtlAware(true)
         .build()
 
-private fun BackgroundModifier.toProto(): ModifiersBuilders.Background =
+private fun BackgroundModifier.toProto(context: Context): ModifiersBuilders.Background =
     ModifiersBuilders.Background.Builder()
-        .setColor(argb(this.color.value.toInt()))
+        .setColor(argb(this.colorProvider.getColor(context).value.toInt()))
         .build()
 
+private fun ColorProvider.getColor(context: Context) = when (this) {
+    is FixedColorProvider -> color
+    is ResourceColorProvider -> resolve(context)
+    else -> error("Unsupported color provider: $this")
+}
+
 private fun LaunchActivityAction.toProto(context: Context): ActionBuilders.LaunchAction =
     ActionBuilders.LaunchAction.Builder()
         .setAndroidActivity(
@@ -392,7 +402,7 @@
 ): ModifiersBuilders.Modifiers =
     modifier.foldIn(ModifiersBuilders.Modifiers.Builder()) { builder, element ->
         when (element) {
-            is BackgroundModifier -> builder.setBackground(element.toProto())
+            is BackgroundModifier -> builder.setBackground(element.toProto(context))
             is WidthModifier -> builder /* Skip for now, handled elsewhere. */
             is HeightModifier -> builder /* Skip for now, handled elsewhere. */
             is ActionModifier -> builder.setClickable(element.toProto(context))
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 a8ddeb4e..fc3acc0 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
@@ -156,6 +156,19 @@
     }
 
     @Test
+    fun canTranslateBackgroundModifier_resId() = fakeCoroutineScope.runBlockingTest {
+        val content = runAndTranslate {
+            Box(modifier = Modifier.background(R.color.color1)) {}
+        }
+
+        val innerBox =
+            (content as LayoutElementBuilders.Box).contents[0] as LayoutElementBuilders.Box
+        val background = requireNotNull(innerBox.modifiers!!.background)
+
+        assertThat(background.color!!.argb).isEqualTo(android.graphics.Color.rgb(0xC0, 0xFF, 0xEE))
+    }
+
+    @Test
     fun canTranslateRow() = fakeCoroutineScope.runBlockingTest {
         val content = runAndTranslate {
             Row(verticalAlignment = Alignment.Vertical.CenterVertically) {
diff --git a/glance/glance-wear/src/test/kotlin/androidx/glance/wear/layout/BackgroundTest.kt b/glance/glance-wear/src/test/kotlin/androidx/glance/wear/layout/BackgroundTest.kt
index a1ae34e..6190d10 100644
--- a/glance/glance-wear/src/test/kotlin/androidx/glance/wear/layout/BackgroundTest.kt
+++ b/glance/glance-wear/src/test/kotlin/androidx/glance/wear/layout/BackgroundTest.kt
@@ -21,8 +21,12 @@
 import androidx.glance.background
 import androidx.glance.findModifier
 import androidx.glance.unit.Color
+import androidx.glance.unit.FixedColorProvider
+import androidx.glance.unit.ResourceColorProvider
+import androidx.glance.wear.test.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
+import kotlin.test.assertIs
 
 class BackgroundTest {
     @Test
@@ -31,6 +35,19 @@
 
         val addedModifier = requireNotNull(modifier.findModifier<BackgroundModifier>())
 
-        assertThat(addedModifier.color.value).isEqualTo(0xFF223344u)
+        val modifierColors = addedModifier.colorProvider
+        assertIs<FixedColorProvider>(modifierColors)
+        assertThat(modifierColors.color.value).isEqualTo(0xFF223344u)
+    }
+
+    @Test
+    fun canUseBackgroundModifier_resId() {
+        val modifier = Modifier.background(color = R.color.color1)
+
+        val addedModifier = requireNotNull(modifier.findModifier<BackgroundModifier>())
+
+        val modifierColors = addedModifier.colorProvider
+        assertIs<ResourceColorProvider>(modifierColors)
+        assertThat(modifierColors.resId).isEqualTo(R.color.color1)
     }
 }
diff --git a/glance/glance-wear/src/test/res/values/colors.xml b/glance/glance-wear/src/test/res/values/colors.xml
new file mode 100644
index 0000000..cd9747d8
--- /dev/null
+++ b/glance/glance-wear/src/test/res/values/colors.xml
@@ -0,0 +1,18 @@
+<?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>
+    <color name="color1">#C0FFEE</color>
+</resources>
\ No newline at end of file
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index 190036e..c53e014 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -3,6 +3,8 @@
 
   public final class BackgroundKt {
     method public static androidx.glance.Modifier background(androidx.glance.Modifier, int color);
+    method public static androidx.glance.Modifier background(androidx.glance.Modifier, @ColorRes int color);
+    method public static androidx.glance.Modifier background(androidx.glance.Modifier, androidx.glance.unit.ColorProvider colorProvider);
   }
 
   public final class CombinedModifier implements androidx.glance.Modifier {
@@ -316,6 +318,14 @@
     method @androidx.compose.runtime.Stable public static int Color(float red, float green, float blue, optional float alpha);
   }
 
+  public interface ColorProvider {
+  }
+
+  public final class ColorProviderKt {
+    method public static androidx.glance.unit.ColorProvider ColorProvider(int color);
+    method public static androidx.glance.unit.ColorProvider ColorProvider(@ColorRes int resId);
+  }
+
   public final inline class Dp implements java.lang.Comparable<androidx.glance.unit.Dp> {
     ctor public Dp();
     method public operator int compareTo(float other);
diff --git a/glance/glance/api/public_plus_experimental_current.txt b/glance/glance/api/public_plus_experimental_current.txt
index 190036e..c53e014 100644
--- a/glance/glance/api/public_plus_experimental_current.txt
+++ b/glance/glance/api/public_plus_experimental_current.txt
@@ -3,6 +3,8 @@
 
   public final class BackgroundKt {
     method public static androidx.glance.Modifier background(androidx.glance.Modifier, int color);
+    method public static androidx.glance.Modifier background(androidx.glance.Modifier, @ColorRes int color);
+    method public static androidx.glance.Modifier background(androidx.glance.Modifier, androidx.glance.unit.ColorProvider colorProvider);
   }
 
   public final class CombinedModifier implements androidx.glance.Modifier {
@@ -316,6 +318,14 @@
     method @androidx.compose.runtime.Stable public static int Color(float red, float green, float blue, optional float alpha);
   }
 
+  public interface ColorProvider {
+  }
+
+  public final class ColorProviderKt {
+    method public static androidx.glance.unit.ColorProvider ColorProvider(int color);
+    method public static androidx.glance.unit.ColorProvider ColorProvider(@ColorRes int resId);
+  }
+
   public final inline class Dp implements java.lang.Comparable<androidx.glance.unit.Dp> {
     ctor public Dp();
     method public operator int compareTo(float other);
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index 190036e..c53e014 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -3,6 +3,8 @@
 
   public final class BackgroundKt {
     method public static androidx.glance.Modifier background(androidx.glance.Modifier, int color);
+    method public static androidx.glance.Modifier background(androidx.glance.Modifier, @ColorRes int color);
+    method public static androidx.glance.Modifier background(androidx.glance.Modifier, androidx.glance.unit.ColorProvider colorProvider);
   }
 
   public final class CombinedModifier implements androidx.glance.Modifier {
@@ -316,6 +318,14 @@
     method @androidx.compose.runtime.Stable public static int Color(float red, float green, float blue, optional float alpha);
   }
 
+  public interface ColorProvider {
+  }
+
+  public final class ColorProviderKt {
+    method public static androidx.glance.unit.ColorProvider ColorProvider(int color);
+    method public static androidx.glance.unit.ColorProvider ColorProvider(@ColorRes int resId);
+  }
+
   public final inline class Dp implements java.lang.Comparable<androidx.glance.unit.Dp> {
     ctor public Dp();
     method public operator int compareTo(float other);
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/Background.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/Background.kt
index b5e3632..72385e9 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/Background.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/Background.kt
@@ -16,13 +16,15 @@
 
 package androidx.glance
 
+import androidx.annotation.ColorRes
 import androidx.annotation.RestrictTo
 import androidx.glance.unit.Color
+import androidx.glance.unit.ColorProvider
 
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class BackgroundModifier(public val color: Color) : Modifier.Element {
-    override fun toString(): String = "BackgroundModifier(color=$color)"
+public class BackgroundModifier(public val colorProvider: ColorProvider) : Modifier.Element {
+    override fun toString(): String = "BackgroundModifier(colorProvider=$colorProvider)"
 }
 
 /**
@@ -30,4 +32,19 @@
  * element to paint the specified [Color] as its background, which will fill the bounds of the
  * element.
  */
-public fun Modifier.background(color: Color): Modifier = this.then(BackgroundModifier(color))
+public fun Modifier.background(color: Color): Modifier = background(ColorProvider(color))
+
+/**
+ * Apply a background color to the element this modifier is attached to. This will cause the
+ * element to paint the specified color resource as its background, which will fill the bounds of
+ * the element.
+ */
+public fun Modifier.background(@ColorRes color: Int): Modifier = background(ColorProvider(color))
+
+/**
+ * Apply a background color to the element this modifier is attached to. This will cause the
+ * element to paint the specified [ColorProvider] as its background, which will fill the bounds of
+ * the element.
+ */
+public fun Modifier.background(colorProvider: ColorProvider): Modifier =
+    this.then(BackgroundModifier(colorProvider))
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/unit/ColorProvider.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/unit/ColorProvider.kt
new file mode 100644
index 0000000..74bbea0
--- /dev/null
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/unit/ColorProvider.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.unit
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+
+/** Provider of colors for a glance composable's attributes. */
+public interface ColorProvider
+
+/** Returns a [ColorProvider] that always resolves to the [Color]. */
+public fun ColorProvider(color: Color): ColorProvider {
+    return FixedColorProvider(color)
+}
+
+/** Returns a [ColorProvider] that resolves to the color resource. */
+public fun ColorProvider(@ColorRes resId: Int): ColorProvider {
+    return ResourceColorProvider(resId)
+}
+
+/** @suppress */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class FixedColorProvider(val color: Color) : ColorProvider
+
+/** @suppress */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class ResourceColorProvider(@ColorRes val resId: Int) : ColorProvider
+
+/** @suppress */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Suppress("INLINE_CLASS_DEPRECATED")
+public fun ResourceColorProvider.resolve(context: Context): Color {
+    val androidColor = if (Build.VERSION.SDK_INT >= 23) {
+        ColorProviderApi23Impl.getColor(context.applicationContext, resId)
+    } else {
+        @Suppress("DEPRECATION") // Resources.getColor must be used on < 23.
+        context.applicationContext.resources.getColor(resId)
+    }
+    return Color(
+        red = android.graphics.Color.red(androidColor) / 255f,
+        green = android.graphics.Color.green(androidColor) / 255f,
+        blue = android.graphics.Color.blue(androidColor) / 255f,
+        alpha = android.graphics.Color.alpha(androidColor) / 255f
+    )
+}
+
+@RequiresApi(23)
+private object ColorProviderApi23Impl {
+    @ColorInt
+    @DoNotInline
+    fun getColor(context: Context, @ColorRes resId: Int): Int {
+        return context.getColor(resId)
+    }
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8887613..0c16be7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -148,7 +148,7 @@
 robolectric = { module = "org.robolectric:robolectric", version = "4.6.1" }
 rxjava2 = { module = "io.reactivex.rxjava2:rxjava", version = "2.2.9" }
 rxjava3 = { module = "io.reactivex.rxjava3:rxjava", version = "3.0.0" }
-shadow = { module = "com.github.jengelman.gradle.plugins:shadow", version = "6.1.0" }
+shadow = { module = "gradle.plugin.com.github.johnrengelman:shadow", version = "7.1.0" }
 skiko = { module = "org.jetbrains.skiko:skiko-jvm", version.ref = "skiko" }
 skikoCommon = { module = "org.jetbrains.skiko:skiko", version.ref = "skiko" }
 skikoMacOsArm64 = { module = "org.jetbrains.skiko:skiko-jvm-runtime-macos-arm64", version.ref = "skiko" }
diff --git a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt
index c247b80..fb17847 100644
--- a/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt
+++ b/inspection/inspection-gradle-plugin/src/main/kotlin/androidx/inspection/gradle/ShadowDependenciesTask.kt
@@ -116,6 +116,10 @@
 class RenameServicesTransformer : Transformer {
     private val renamed = mutableMapOf<String, String>()
 
+    override fun getName(): String {
+        return "RenameServicesTransformer"
+    }
+
     override fun canTransformResource(element: FileTreeElement?): Boolean {
         return element?.relativePath?.startsWith("META-INF/services") ?: false
     }
diff --git a/media/OWNERS b/media/OWNERS
index 35b2d68..6456ac8 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -5,3 +5,8 @@
 jaewan@google.com
 jinpark@google.com
 sungsoo@google.com
+olly@google.com
+tonihei@google.com
+christosts@google.com
+bachinger@google.com
+ibaker@google.com
diff --git a/media2/OWNERS b/media2/OWNERS
index 4b983fcb..afe4bcd 100644
--- a/media2/OWNERS
+++ b/media2/OWNERS
@@ -6,4 +6,9 @@
 jaewan@google.com
 jinpark@google.com
 sungsoo@google.com
+olly@google.com
+tonihei@google.com
+christosts@google.com
+bachinger@google.com
+ibaker@google.com
 
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java
index 5572e7d..ed8fd9a 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java
@@ -168,7 +168,6 @@
  * @see MediaSessionService
  */
 public class MediaSession implements Closeable {
-    static final String TAG = "MediaSession";
 
     // It's better to have private static lock instead of using MediaSession.class because the
     // private lock object isn't exposed.
diff --git a/mediarouter/OWNERS b/mediarouter/OWNERS
index ec7a0e2..5ac7be6 100644
--- a/mediarouter/OWNERS
+++ b/mediarouter/OWNERS
@@ -6,3 +6,8 @@
 jinpark@google.com
 klhyun@google.com
 sungsoo@google.com
+olly@google.com
+tonihei@google.com
+christosts@google.com
+bachinger@google.com
+ibaker@google.com
diff --git a/metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi16Impl.kt b/metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi16Impl.kt
index e79f05d..8ae92ce4 100644
--- a/metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi16Impl.kt
+++ b/metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi16Impl.kt
@@ -53,11 +53,11 @@
             @SuppressLint("ClassVerificationFailure")
             override fun onPreDraw(): Boolean {
                 val decorView = decorViewRef.get()
+                val frameStart = getFrameStartTime()
                 decorView?.let {
                     decorView.handler.sendMessageAtFrontOfQueue(
                         Message.obtain(decorView.handler) {
                             val now = System.nanoTime()
-                            val frameStart = getFrameStartTime()
                             val expectedDuration = getExpectedFrameDuration(decorView) *
                                 JankStats.jankHeuristicMultiplier
                             jankStats.logFrameData(
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationAndroidTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationAndroidTest.kt
index 5f4d540..0d7278f 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationAndroidTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationAndroidTest.kt
@@ -93,6 +93,21 @@
     }
 
     @Test
+    fun addDeepLinkNullableArgumentNotRequired() {
+        val destination = NoOpNavigator().createDestination()
+        destination.addArgument("myArg", nullableStringArgument())
+        destination.addDeepLink("www.example.com/users?myArg={myArg}")
+
+        val match = destination.matchDeepLink(
+            Uri.parse("https://www.example.com/users?")
+        )
+
+        assertWithMessage("Deep link should match")
+            .that(match)
+            .isNotNull()
+    }
+
+    @Test
     fun matchDeepLink() {
         val destination = NoOpNavigator().createDestination()
         destination.addArgument("id", intArgument())
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
index 3a2663e..f96f6e8 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
@@ -315,9 +315,10 @@
      * @see NavController.navigate
      */
     public fun addDeepLink(navDeepLink: NavDeepLink) {
-        val missingRequiredArguments = arguments.filterValues { !it.isDefaultValuePresent }
-            .keys
-            .filter { it !in navDeepLink.argumentsNames }
+        val missingRequiredArguments =
+            arguments.filterValues { !it.isNullable && !it.isDefaultValuePresent }
+                .keys
+                .filter { it !in navDeepLink.argumentsNames }
         require(missingRequiredArguments.isEmpty()) {
             "Deep link ${navDeepLink.uriPattern} can't be used to open destination $this.\n" +
                 "Following required arguments are missing: $missingRequiredArguments"
diff --git a/paging/paging-common-ktx/api/3.1.0-beta02.txt b/paging/paging-common-ktx/api/3.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-common-ktx/api/3.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-common-ktx/api/public_plus_experimental_3.1.0-beta02.txt b/paging/paging-common-ktx/api/public_plus_experimental_3.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-common-ktx/api/public_plus_experimental_3.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-common-ktx/api/restricted_3.1.0-beta02.txt b/paging/paging-common-ktx/api/restricted_3.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-common-ktx/api/restricted_3.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-common/api/3.1.0-beta02.txt b/paging/paging-common/api/3.1.0-beta02.txt
new file mode 100644
index 0000000..cb81fc4
--- /dev/null
+++ b/paging/paging-common/api/3.1.0-beta02.txt
@@ -0,0 +1,470 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class CachedPagingDataKt {
+    method @CheckResult public static <T> kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>> cachedIn(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+  }
+
+  public final class CancelableChannelFlowKt {
+  }
+
+  public final class CombinedLoadStates {
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, optional androidx.paging.LoadStates? mediator);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadStates? getMediator();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    method public androidx.paging.LoadStates getSource();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadStates? mediator;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+    property public final androidx.paging.LoadStates source;
+  }
+
+  public abstract class DataSource<Key, Value> {
+    method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    method @AnyThread public void invalidate();
+    method @WorkerThread public boolean isInvalid();
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    property @WorkerThread public boolean isInvalid;
+  }
+
+  public abstract static class DataSource.Factory<Key, Value> {
+    ctor public DataSource.Factory();
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory(optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory();
+    method public abstract androidx.paging.DataSource<Key,Value> create();
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+  }
+
+  public static fun interface DataSource.InvalidatedCallback {
+    method @AnyThread public void onInvalidated();
+  }
+
+  public final class FlowExtKt {
+  }
+
+  public final class HintHandlerKt {
+  }
+
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public void invalidate();
+    method public androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
+  @Deprecated public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public ItemKeyedDataSource();
+    method @Deprecated public abstract Key getKey(Value item);
+    method @Deprecated public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final Key? requestedInitialKey;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T> {
+    ctor public ItemSnapshotList(@IntRange(from=0) int placeholdersBefore, @IntRange(from=0) int placeholdersAfter, java.util.List<? extends T> items);
+    method public T? get(int index);
+    method public java.util.List<T> getItems();
+    method public int getPlaceholdersAfter();
+    method public int getPlaceholdersBefore();
+    method public int getSize();
+    property public final java.util.List<T> items;
+    property public final int placeholdersAfter;
+    property public final int placeholdersBefore;
+    property public int size;
+  }
+
+  public abstract sealed class LoadState {
+    method public final boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public static final class LoadState.Error extends androidx.paging.LoadState {
+    ctor public LoadState.Error(Throwable error);
+    method public Throwable getError();
+    property public final Throwable error;
+  }
+
+  public static final class LoadState.Loading extends androidx.paging.LoadState {
+    field public static final androidx.paging.LoadState.Loading INSTANCE;
+  }
+
+  public static final class LoadState.NotLoading extends androidx.paging.LoadState {
+    ctor public LoadState.NotLoading(boolean endOfPaginationReached);
+  }
+
+  public final class LoadStates {
+    ctor public LoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState component1();
+    method public androidx.paging.LoadState component2();
+    method public androidx.paging.LoadState component3();
+    method public androidx.paging.LoadStates copy(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+  }
+
+  public enum LoadType {
+    enum_constant public static final androidx.paging.LoadType APPEND;
+    enum_constant public static final androidx.paging.LoadType PREPEND;
+    enum_constant public static final androidx.paging.LoadType REFRESH;
+  }
+
+  public final class PageFetcherSnapshotKt {
+  }
+
+  @Deprecated public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public PageKeyedDataSource();
+    method @Deprecated public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public abstract class PagedList<T> extends java.util.AbstractList<T> {
+    method @Deprecated public final void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public abstract void detach();
+    method @Deprecated public T? get(int index);
+    method @Deprecated public final androidx.paging.PagedList.Config getConfig();
+    method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
+    method @Deprecated public abstract Object? getLastKey();
+    method @Deprecated public final int getLoadedCount();
+    method @Deprecated public final int getPositionOffset();
+    method @Deprecated public int getSize();
+    method @Deprecated public abstract boolean isDetached();
+    method @Deprecated public boolean isImmutable();
+    method @Deprecated public final void loadAround(int index);
+    method @Deprecated public final void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void retry();
+    method @Deprecated public final java.util.List<T> snapshot();
+    property public final androidx.paging.PagedList.Config config;
+    property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
+    property public abstract boolean isDetached;
+    property public boolean isImmutable;
+    property public abstract Object? lastKey;
+    property public final int loadedCount;
+    property public final int positionOffset;
+    property public int size;
+  }
+
+  @Deprecated @MainThread public abstract static class PagedList.BoundaryCallback<T> {
+    ctor @Deprecated public PagedList.BoundaryCallback();
+    method @Deprecated public void onItemAtEndLoaded(T itemAtEnd);
+    method @Deprecated public void onItemAtFrontLoaded(T itemAtFront);
+    method @Deprecated public void onZeroItemsLoaded();
+  }
+
+  @Deprecated public static final class PagedList.Builder<Key, Value> {
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, int pageSize);
+    method @Deprecated public androidx.paging.PagedList<Value> build();
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+  }
+
+  @Deprecated public abstract static class PagedList.Callback {
+    ctor @Deprecated public PagedList.Callback();
+    method @Deprecated public abstract void onChanged(int position, int count);
+    method @Deprecated public abstract void onInserted(int position, int count);
+    method @Deprecated public abstract void onRemoved(int position, int count);
+  }
+
+  @Deprecated public static final class PagedList.Config {
+    field @Deprecated public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field @Deprecated public final boolean enablePlaceholders;
+    field @Deprecated public final int initialLoadSizeHint;
+    field @Deprecated public final int maxSize;
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final int prefetchDistance;
+  }
+
+  @Deprecated public static final class PagedList.Config.Builder {
+    ctor @Deprecated public PagedList.Config.Builder();
+    method @Deprecated public androidx.paging.PagedList.Config build();
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int initialLoadSizeHint);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int maxSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int pageSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
+  }
+
+  public final class PagedListConfigKt {
+  }
+
+  public final class PagedListKt {
+  }
+
+  public final class Pager<Key, Value> {
+    ctor public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> getFlow();
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> flow;
+  }
+
+  public final class PagingConfig {
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize, optional int jumpThreshold);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance);
+    ctor public PagingConfig(int pageSize);
+    field public static final androidx.paging.PagingConfig.Companion Companion;
+    field public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field public final boolean enablePlaceholders;
+    field public final int initialLoadSize;
+    field public final int jumpThreshold;
+    field public final int maxSize;
+    field public final int pageSize;
+    field public final int prefetchDistance;
+  }
+
+  public static final class PagingConfig.Companion {
+  }
+
+  public final class PagingData<T> {
+    method public static <T> androidx.paging.PagingData<T> empty();
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+    field public static final androidx.paging.PagingData.Companion Companion;
+  }
+
+  public static final class PagingData.Companion {
+    method public <T> androidx.paging.PagingData<T> empty();
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+  }
+
+  public final class PagingDataTransforms {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends java.lang.Iterable<? extends R>> transform);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
+  }
+
+  public abstract class PagingSource<Key, Value> {
+    ctor public PagingSource();
+    method public final boolean getInvalid();
+    method public boolean getJumpingSupported();
+    method public boolean getKeyReuseSupported();
+    method public abstract Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
+    method public final void invalidate();
+    method public abstract suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    property public final boolean invalid;
+    property public boolean jumpingSupported;
+    property public boolean keyReuseSupported;
+  }
+
+  public abstract static sealed class PagingSource.LoadParams<Key> {
+    method public abstract Key? getKey();
+    method public final int getLoadSize();
+    method public final boolean getPlaceholdersEnabled();
+    property public abstract Key? key;
+    property public final int loadSize;
+    property public final boolean placeholdersEnabled;
+  }
+
+  public static final class PagingSource.LoadParams.Append<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Append(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Prepend<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Prepend(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Refresh<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
+    method public Key? getKey();
+    property public Key? key;
+  }
+
+  public abstract static sealed class PagingSource.LoadResult<Key, Value> {
+  }
+
+  public static final class PagingSource.LoadResult.Error<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Error(Throwable throwable);
+    method public Throwable component1();
+    method public androidx.paging.PagingSource.LoadResult.Error<Key,Value> copy(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class PagingSource.LoadResult.Invalid<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Invalid();
+  }
+
+  public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsAfter);
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
+    method public java.util.List<Value> component1();
+    method public Key? component2();
+    method public Key? component3();
+    method public int component4();
+    method public int component5();
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value> copy(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, @IntRange(from=-2147483648L) int itemsBefore, @IntRange(from=-2147483648L) int itemsAfter);
+    method public java.util.List<Value> getData();
+    method public int getItemsAfter();
+    method public int getItemsBefore();
+    method public Key? getNextKey();
+    method public Key? getPrevKey();
+    property public final java.util.List<Value> data;
+    property public final int itemsAfter;
+    property public final int itemsBefore;
+    property public final Key? nextKey;
+    property public final Key? prevKey;
+    field public static final int COUNT_UNDEFINED = -2147483648; // 0x80000000
+    field public static final androidx.paging.PagingSource.LoadResult.Page.Companion Companion;
+  }
+
+  public static final class PagingSource.LoadResult.Page.Companion {
+  }
+
+  public final class PagingSourceKt {
+  }
+
+  public final class PagingState<Key, Value> {
+    ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0) int leadingPlaceholderCount);
+    method public Value? closestItemToPosition(int anchorPosition);
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value>? closestPageToPosition(int anchorPosition);
+    method public Value? firstItemOrNull();
+    method public Integer? getAnchorPosition();
+    method public androidx.paging.PagingConfig getConfig();
+    method public java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> getPages();
+    method public boolean isEmpty();
+    method public Value? lastItemOrNull();
+    property public final Integer? anchorPosition;
+    property public final androidx.paging.PagingConfig config;
+    property public final java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages;
+  }
+
+  @Deprecated public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+    ctor @Deprecated public PositionalDataSource();
+    method @Deprecated public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+    method @Deprecated public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+    method @Deprecated @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+    method @Deprecated @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(kotlin.jvm.functions.Function1<? super T,? extends V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends T>,? extends java.util.List<? extends V>> function);
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadInitialCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadInitialParams {
+    ctor @Deprecated public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+    field @Deprecated public final int requestedStartPosition;
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadRangeCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadRangeCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadRangeParams {
+    ctor @Deprecated public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+    field @Deprecated public final int loadSize;
+    field @Deprecated public final int startPosition;
+  }
+
+  public final class RemoteMediatorAccessorKt {
+  }
+
+  public final class SeparatorsKt {
+  }
+
+  public final class SimpleChannelFlowKt {
+  }
+
+  public enum TerminalSeparatorType {
+    enum_constant public static final androidx.paging.TerminalSeparatorType FULLY_COMPLETE;
+    enum_constant public static final androidx.paging.TerminalSeparatorType SOURCE_COMPLETE;
+  }
+
+}
+
diff --git a/paging/paging-common/api/public_plus_experimental_3.1.0-beta02.txt b/paging/paging-common/api/public_plus_experimental_3.1.0-beta02.txt
new file mode 100644
index 0000000..059daf7
--- /dev/null
+++ b/paging/paging-common/api/public_plus_experimental_3.1.0-beta02.txt
@@ -0,0 +1,500 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class CachedPagingDataKt {
+    method @CheckResult public static <T> kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>> cachedIn(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+  }
+
+  public final class CancelableChannelFlowKt {
+  }
+
+  public final class CombinedLoadStates {
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, optional androidx.paging.LoadStates? mediator);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadStates? getMediator();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    method public androidx.paging.LoadStates getSource();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadStates? mediator;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+    property public final androidx.paging.LoadStates source;
+  }
+
+  public abstract class DataSource<Key, Value> {
+    method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    method @AnyThread public void invalidate();
+    method @WorkerThread public boolean isInvalid();
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    property @WorkerThread public boolean isInvalid;
+  }
+
+  public abstract static class DataSource.Factory<Key, Value> {
+    ctor public DataSource.Factory();
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory(optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory();
+    method public abstract androidx.paging.DataSource<Key,Value> create();
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+  }
+
+  public static fun interface DataSource.InvalidatedCallback {
+    method @AnyThread public void onInvalidated();
+  }
+
+  @kotlin.RequiresOptIn public @interface ExperimentalPagingApi {
+  }
+
+  public final class FlowExtKt {
+  }
+
+  public final class HintHandlerKt {
+  }
+
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public void invalidate();
+    method public androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
+  @Deprecated public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public ItemKeyedDataSource();
+    method @Deprecated public abstract Key getKey(Value item);
+    method @Deprecated public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final Key? requestedInitialKey;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T> {
+    ctor public ItemSnapshotList(@IntRange(from=0) int placeholdersBefore, @IntRange(from=0) int placeholdersAfter, java.util.List<? extends T> items);
+    method public T? get(int index);
+    method public java.util.List<T> getItems();
+    method public int getPlaceholdersAfter();
+    method public int getPlaceholdersBefore();
+    method public int getSize();
+    property public final java.util.List<T> items;
+    property public final int placeholdersAfter;
+    property public final int placeholdersBefore;
+    property public int size;
+  }
+
+  public abstract sealed class LoadState {
+    method public final boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public static final class LoadState.Error extends androidx.paging.LoadState {
+    ctor public LoadState.Error(Throwable error);
+    method public Throwable getError();
+    property public final Throwable error;
+  }
+
+  public static final class LoadState.Loading extends androidx.paging.LoadState {
+    field public static final androidx.paging.LoadState.Loading INSTANCE;
+  }
+
+  public static final class LoadState.NotLoading extends androidx.paging.LoadState {
+    ctor public LoadState.NotLoading(boolean endOfPaginationReached);
+  }
+
+  public final class LoadStates {
+    ctor public LoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState component1();
+    method public androidx.paging.LoadState component2();
+    method public androidx.paging.LoadState component3();
+    method public androidx.paging.LoadStates copy(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+  }
+
+  public enum LoadType {
+    enum_constant public static final androidx.paging.LoadType APPEND;
+    enum_constant public static final androidx.paging.LoadType PREPEND;
+    enum_constant public static final androidx.paging.LoadType REFRESH;
+  }
+
+  public final class PageFetcherSnapshotKt {
+  }
+
+  @Deprecated public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public PageKeyedDataSource();
+    method @Deprecated public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public abstract class PagedList<T> extends java.util.AbstractList<T> {
+    method @Deprecated public final void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public abstract void detach();
+    method @Deprecated public T? get(int index);
+    method @Deprecated public final androidx.paging.PagedList.Config getConfig();
+    method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
+    method @Deprecated public abstract Object? getLastKey();
+    method @Deprecated public final int getLoadedCount();
+    method @Deprecated public final int getPositionOffset();
+    method @Deprecated public int getSize();
+    method @Deprecated public abstract boolean isDetached();
+    method @Deprecated public boolean isImmutable();
+    method @Deprecated public final void loadAround(int index);
+    method @Deprecated public final void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void retry();
+    method @Deprecated public final java.util.List<T> snapshot();
+    property public final androidx.paging.PagedList.Config config;
+    property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
+    property public abstract boolean isDetached;
+    property public boolean isImmutable;
+    property public abstract Object? lastKey;
+    property public final int loadedCount;
+    property public final int positionOffset;
+    property public int size;
+  }
+
+  @Deprecated @MainThread public abstract static class PagedList.BoundaryCallback<T> {
+    ctor @Deprecated public PagedList.BoundaryCallback();
+    method @Deprecated public void onItemAtEndLoaded(T itemAtEnd);
+    method @Deprecated public void onItemAtFrontLoaded(T itemAtFront);
+    method @Deprecated public void onZeroItemsLoaded();
+  }
+
+  @Deprecated public static final class PagedList.Builder<Key, Value> {
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, int pageSize);
+    method @Deprecated public androidx.paging.PagedList<Value> build();
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+  }
+
+  @Deprecated public abstract static class PagedList.Callback {
+    ctor @Deprecated public PagedList.Callback();
+    method @Deprecated public abstract void onChanged(int position, int count);
+    method @Deprecated public abstract void onInserted(int position, int count);
+    method @Deprecated public abstract void onRemoved(int position, int count);
+  }
+
+  @Deprecated public static final class PagedList.Config {
+    field @Deprecated public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field @Deprecated public final boolean enablePlaceholders;
+    field @Deprecated public final int initialLoadSizeHint;
+    field @Deprecated public final int maxSize;
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final int prefetchDistance;
+  }
+
+  @Deprecated public static final class PagedList.Config.Builder {
+    ctor @Deprecated public PagedList.Config.Builder();
+    method @Deprecated public androidx.paging.PagedList.Config build();
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int initialLoadSizeHint);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int maxSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int pageSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
+  }
+
+  public final class PagedListConfigKt {
+  }
+
+  public final class PagedListKt {
+  }
+
+  public final class Pager<Key, Value> {
+    ctor @androidx.paging.ExperimentalPagingApi public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, androidx.paging.RemoteMediator<Key,Value>? remoteMediator, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> getFlow();
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> flow;
+  }
+
+  public final class PagingConfig {
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize, optional int jumpThreshold);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance);
+    ctor public PagingConfig(int pageSize);
+    field public static final androidx.paging.PagingConfig.Companion Companion;
+    field public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field public final boolean enablePlaceholders;
+    field public final int initialLoadSize;
+    field public final int jumpThreshold;
+    field public final int maxSize;
+    field public final int pageSize;
+    field public final int prefetchDistance;
+  }
+
+  public static final class PagingConfig.Companion {
+  }
+
+  public final class PagingData<T> {
+    method public static <T> androidx.paging.PagingData<T> empty();
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+    field public static final androidx.paging.PagingData.Companion Companion;
+  }
+
+  public static final class PagingData.Companion {
+    method public <T> androidx.paging.PagingData<T> empty();
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+  }
+
+  public final class PagingDataTransforms {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends java.lang.Iterable<? extends R>> transform);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
+  }
+
+  public abstract class PagingSource<Key, Value> {
+    ctor public PagingSource();
+    method public final boolean getInvalid();
+    method public boolean getJumpingSupported();
+    method public boolean getKeyReuseSupported();
+    method public abstract Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
+    method public final void invalidate();
+    method public abstract suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    property public final boolean invalid;
+    property public boolean jumpingSupported;
+    property public boolean keyReuseSupported;
+  }
+
+  public abstract static sealed class PagingSource.LoadParams<Key> {
+    method public abstract Key? getKey();
+    method public final int getLoadSize();
+    method public final boolean getPlaceholdersEnabled();
+    property public abstract Key? key;
+    property public final int loadSize;
+    property public final boolean placeholdersEnabled;
+  }
+
+  public static final class PagingSource.LoadParams.Append<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Append(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Prepend<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Prepend(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Refresh<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
+    method public Key? getKey();
+    property public Key? key;
+  }
+
+  public abstract static sealed class PagingSource.LoadResult<Key, Value> {
+  }
+
+  public static final class PagingSource.LoadResult.Error<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Error(Throwable throwable);
+    method public Throwable component1();
+    method public androidx.paging.PagingSource.LoadResult.Error<Key,Value> copy(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class PagingSource.LoadResult.Invalid<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Invalid();
+  }
+
+  public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsAfter);
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
+    method public java.util.List<Value> component1();
+    method public Key? component2();
+    method public Key? component3();
+    method public int component4();
+    method public int component5();
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value> copy(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, @IntRange(from=-2147483648L) int itemsBefore, @IntRange(from=-2147483648L) int itemsAfter);
+    method public java.util.List<Value> getData();
+    method public int getItemsAfter();
+    method public int getItemsBefore();
+    method public Key? getNextKey();
+    method public Key? getPrevKey();
+    property public final java.util.List<Value> data;
+    property public final int itemsAfter;
+    property public final int itemsBefore;
+    property public final Key? nextKey;
+    property public final Key? prevKey;
+    field public static final int COUNT_UNDEFINED = -2147483648; // 0x80000000
+    field public static final androidx.paging.PagingSource.LoadResult.Page.Companion Companion;
+  }
+
+  public static final class PagingSource.LoadResult.Page.Companion {
+  }
+
+  public final class PagingSourceKt {
+  }
+
+  public final class PagingState<Key, Value> {
+    ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0) int leadingPlaceholderCount);
+    method public Value? closestItemToPosition(int anchorPosition);
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value>? closestPageToPosition(int anchorPosition);
+    method public Value? firstItemOrNull();
+    method public Integer? getAnchorPosition();
+    method public androidx.paging.PagingConfig getConfig();
+    method public java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> getPages();
+    method public boolean isEmpty();
+    method public Value? lastItemOrNull();
+    property public final Integer? anchorPosition;
+    property public final androidx.paging.PagingConfig config;
+    property public final java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages;
+  }
+
+  @Deprecated public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+    ctor @Deprecated public PositionalDataSource();
+    method @Deprecated public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+    method @Deprecated public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+    method @Deprecated @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+    method @Deprecated @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(kotlin.jvm.functions.Function1<? super T,? extends V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends T>,? extends java.util.List<? extends V>> function);
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadInitialCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadInitialParams {
+    ctor @Deprecated public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+    field @Deprecated public final int requestedStartPosition;
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadRangeCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadRangeCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadRangeParams {
+    ctor @Deprecated public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+    field @Deprecated public final int loadSize;
+    field @Deprecated public final int startPosition;
+  }
+
+  @androidx.paging.ExperimentalPagingApi public abstract class RemoteMediator<Key, Value> {
+    ctor public RemoteMediator();
+    method public suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+    method public abstract suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+  }
+
+  public enum RemoteMediator.InitializeAction {
+    enum_constant public static final androidx.paging.RemoteMediator.InitializeAction LAUNCH_INITIAL_REFRESH;
+    enum_constant public static final androidx.paging.RemoteMediator.InitializeAction SKIP_INITIAL_REFRESH;
+  }
+
+  public abstract static sealed class RemoteMediator.MediatorResult {
+  }
+
+  public static final class RemoteMediator.MediatorResult.Error extends androidx.paging.RemoteMediator.MediatorResult {
+    ctor public RemoteMediator.MediatorResult.Error(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class RemoteMediator.MediatorResult.Success extends androidx.paging.RemoteMediator.MediatorResult {
+    ctor public RemoteMediator.MediatorResult.Success(boolean endOfPaginationReached);
+    method public boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public final class RemoteMediatorAccessorKt {
+  }
+
+  public final class SeparatorsKt {
+  }
+
+  public final class SimpleChannelFlowKt {
+  }
+
+  public enum TerminalSeparatorType {
+    enum_constant public static final androidx.paging.TerminalSeparatorType FULLY_COMPLETE;
+    enum_constant public static final androidx.paging.TerminalSeparatorType SOURCE_COMPLETE;
+  }
+
+}
+
diff --git a/paging/paging-common/api/restricted_3.1.0-beta02.txt b/paging/paging-common/api/restricted_3.1.0-beta02.txt
new file mode 100644
index 0000000..cb81fc4
--- /dev/null
+++ b/paging/paging-common/api/restricted_3.1.0-beta02.txt
@@ -0,0 +1,470 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class CachedPagingDataKt {
+    method @CheckResult public static <T> kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>> cachedIn(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+  }
+
+  public final class CancelableChannelFlowKt {
+  }
+
+  public final class CombinedLoadStates {
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, optional androidx.paging.LoadStates? mediator);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadStates? getMediator();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    method public androidx.paging.LoadStates getSource();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadStates? mediator;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+    property public final androidx.paging.LoadStates source;
+  }
+
+  public abstract class DataSource<Key, Value> {
+    method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    method @AnyThread public void invalidate();
+    method @WorkerThread public boolean isInvalid();
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    property @WorkerThread public boolean isInvalid;
+  }
+
+  public abstract static class DataSource.Factory<Key, Value> {
+    ctor public DataSource.Factory();
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory(optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory();
+    method public abstract androidx.paging.DataSource<Key,Value> create();
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+  }
+
+  public static fun interface DataSource.InvalidatedCallback {
+    method @AnyThread public void onInvalidated();
+  }
+
+  public final class FlowExtKt {
+  }
+
+  public final class HintHandlerKt {
+  }
+
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public void invalidate();
+    method public androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
+  @Deprecated public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public ItemKeyedDataSource();
+    method @Deprecated public abstract Key getKey(Value item);
+    method @Deprecated public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final Key? requestedInitialKey;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T> {
+    ctor public ItemSnapshotList(@IntRange(from=0) int placeholdersBefore, @IntRange(from=0) int placeholdersAfter, java.util.List<? extends T> items);
+    method public T? get(int index);
+    method public java.util.List<T> getItems();
+    method public int getPlaceholdersAfter();
+    method public int getPlaceholdersBefore();
+    method public int getSize();
+    property public final java.util.List<T> items;
+    property public final int placeholdersAfter;
+    property public final int placeholdersBefore;
+    property public int size;
+  }
+
+  public abstract sealed class LoadState {
+    method public final boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public static final class LoadState.Error extends androidx.paging.LoadState {
+    ctor public LoadState.Error(Throwable error);
+    method public Throwable getError();
+    property public final Throwable error;
+  }
+
+  public static final class LoadState.Loading extends androidx.paging.LoadState {
+    field public static final androidx.paging.LoadState.Loading INSTANCE;
+  }
+
+  public static final class LoadState.NotLoading extends androidx.paging.LoadState {
+    ctor public LoadState.NotLoading(boolean endOfPaginationReached);
+  }
+
+  public final class LoadStates {
+    ctor public LoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState component1();
+    method public androidx.paging.LoadState component2();
+    method public androidx.paging.LoadState component3();
+    method public androidx.paging.LoadStates copy(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+  }
+
+  public enum LoadType {
+    enum_constant public static final androidx.paging.LoadType APPEND;
+    enum_constant public static final androidx.paging.LoadType PREPEND;
+    enum_constant public static final androidx.paging.LoadType REFRESH;
+  }
+
+  public final class PageFetcherSnapshotKt {
+  }
+
+  @Deprecated public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public PageKeyedDataSource();
+    method @Deprecated public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public abstract class PagedList<T> extends java.util.AbstractList<T> {
+    method @Deprecated public final void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public abstract void detach();
+    method @Deprecated public T? get(int index);
+    method @Deprecated public final androidx.paging.PagedList.Config getConfig();
+    method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
+    method @Deprecated public abstract Object? getLastKey();
+    method @Deprecated public final int getLoadedCount();
+    method @Deprecated public final int getPositionOffset();
+    method @Deprecated public int getSize();
+    method @Deprecated public abstract boolean isDetached();
+    method @Deprecated public boolean isImmutable();
+    method @Deprecated public final void loadAround(int index);
+    method @Deprecated public final void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void retry();
+    method @Deprecated public final java.util.List<T> snapshot();
+    property public final androidx.paging.PagedList.Config config;
+    property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
+    property public abstract boolean isDetached;
+    property public boolean isImmutable;
+    property public abstract Object? lastKey;
+    property public final int loadedCount;
+    property public final int positionOffset;
+    property public int size;
+  }
+
+  @Deprecated @MainThread public abstract static class PagedList.BoundaryCallback<T> {
+    ctor @Deprecated public PagedList.BoundaryCallback();
+    method @Deprecated public void onItemAtEndLoaded(T itemAtEnd);
+    method @Deprecated public void onItemAtFrontLoaded(T itemAtFront);
+    method @Deprecated public void onZeroItemsLoaded();
+  }
+
+  @Deprecated public static final class PagedList.Builder<Key, Value> {
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, int pageSize);
+    method @Deprecated public androidx.paging.PagedList<Value> build();
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+  }
+
+  @Deprecated public abstract static class PagedList.Callback {
+    ctor @Deprecated public PagedList.Callback();
+    method @Deprecated public abstract void onChanged(int position, int count);
+    method @Deprecated public abstract void onInserted(int position, int count);
+    method @Deprecated public abstract void onRemoved(int position, int count);
+  }
+
+  @Deprecated public static final class PagedList.Config {
+    field @Deprecated public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field @Deprecated public final boolean enablePlaceholders;
+    field @Deprecated public final int initialLoadSizeHint;
+    field @Deprecated public final int maxSize;
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final int prefetchDistance;
+  }
+
+  @Deprecated public static final class PagedList.Config.Builder {
+    ctor @Deprecated public PagedList.Config.Builder();
+    method @Deprecated public androidx.paging.PagedList.Config build();
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int initialLoadSizeHint);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int maxSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int pageSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
+  }
+
+  public final class PagedListConfigKt {
+  }
+
+  public final class PagedListKt {
+  }
+
+  public final class Pager<Key, Value> {
+    ctor public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> getFlow();
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> flow;
+  }
+
+  public final class PagingConfig {
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize, optional int jumpThreshold);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance);
+    ctor public PagingConfig(int pageSize);
+    field public static final androidx.paging.PagingConfig.Companion Companion;
+    field public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field public final boolean enablePlaceholders;
+    field public final int initialLoadSize;
+    field public final int jumpThreshold;
+    field public final int maxSize;
+    field public final int pageSize;
+    field public final int prefetchDistance;
+  }
+
+  public static final class PagingConfig.Companion {
+  }
+
+  public final class PagingData<T> {
+    method public static <T> androidx.paging.PagingData<T> empty();
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+    field public static final androidx.paging.PagingData.Companion Companion;
+  }
+
+  public static final class PagingData.Companion {
+    method public <T> androidx.paging.PagingData<T> empty();
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+  }
+
+  public final class PagingDataTransforms {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends java.lang.Iterable<? extends R>> transform);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
+  }
+
+  public abstract class PagingSource<Key, Value> {
+    ctor public PagingSource();
+    method public final boolean getInvalid();
+    method public boolean getJumpingSupported();
+    method public boolean getKeyReuseSupported();
+    method public abstract Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
+    method public final void invalidate();
+    method public abstract suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    property public final boolean invalid;
+    property public boolean jumpingSupported;
+    property public boolean keyReuseSupported;
+  }
+
+  public abstract static sealed class PagingSource.LoadParams<Key> {
+    method public abstract Key? getKey();
+    method public final int getLoadSize();
+    method public final boolean getPlaceholdersEnabled();
+    property public abstract Key? key;
+    property public final int loadSize;
+    property public final boolean placeholdersEnabled;
+  }
+
+  public static final class PagingSource.LoadParams.Append<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Append(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Prepend<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Prepend(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Refresh<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
+    method public Key? getKey();
+    property public Key? key;
+  }
+
+  public abstract static sealed class PagingSource.LoadResult<Key, Value> {
+  }
+
+  public static final class PagingSource.LoadResult.Error<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Error(Throwable throwable);
+    method public Throwable component1();
+    method public androidx.paging.PagingSource.LoadResult.Error<Key,Value> copy(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class PagingSource.LoadResult.Invalid<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Invalid();
+  }
+
+  public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsAfter);
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
+    method public java.util.List<Value> component1();
+    method public Key? component2();
+    method public Key? component3();
+    method public int component4();
+    method public int component5();
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value> copy(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, @IntRange(from=-2147483648L) int itemsBefore, @IntRange(from=-2147483648L) int itemsAfter);
+    method public java.util.List<Value> getData();
+    method public int getItemsAfter();
+    method public int getItemsBefore();
+    method public Key? getNextKey();
+    method public Key? getPrevKey();
+    property public final java.util.List<Value> data;
+    property public final int itemsAfter;
+    property public final int itemsBefore;
+    property public final Key? nextKey;
+    property public final Key? prevKey;
+    field public static final int COUNT_UNDEFINED = -2147483648; // 0x80000000
+    field public static final androidx.paging.PagingSource.LoadResult.Page.Companion Companion;
+  }
+
+  public static final class PagingSource.LoadResult.Page.Companion {
+  }
+
+  public final class PagingSourceKt {
+  }
+
+  public final class PagingState<Key, Value> {
+    ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0) int leadingPlaceholderCount);
+    method public Value? closestItemToPosition(int anchorPosition);
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value>? closestPageToPosition(int anchorPosition);
+    method public Value? firstItemOrNull();
+    method public Integer? getAnchorPosition();
+    method public androidx.paging.PagingConfig getConfig();
+    method public java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> getPages();
+    method public boolean isEmpty();
+    method public Value? lastItemOrNull();
+    property public final Integer? anchorPosition;
+    property public final androidx.paging.PagingConfig config;
+    property public final java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages;
+  }
+
+  @Deprecated public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+    ctor @Deprecated public PositionalDataSource();
+    method @Deprecated public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+    method @Deprecated public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+    method @Deprecated @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+    method @Deprecated @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(kotlin.jvm.functions.Function1<? super T,? extends V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends T>,? extends java.util.List<? extends V>> function);
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadInitialCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadInitialParams {
+    ctor @Deprecated public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+    field @Deprecated public final int requestedStartPosition;
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadRangeCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadRangeCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadRangeParams {
+    ctor @Deprecated public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+    field @Deprecated public final int loadSize;
+    field @Deprecated public final int startPosition;
+  }
+
+  public final class RemoteMediatorAccessorKt {
+  }
+
+  public final class SeparatorsKt {
+  }
+
+  public final class SimpleChannelFlowKt {
+  }
+
+  public enum TerminalSeparatorType {
+    enum_constant public static final androidx.paging.TerminalSeparatorType FULLY_COMPLETE;
+    enum_constant public static final androidx.paging.TerminalSeparatorType SOURCE_COMPLETE;
+  }
+
+}
+
diff --git a/paging/paging-guava/api/3.1.0-beta02.txt b/paging/paging-guava/api/3.1.0-beta02.txt
new file mode 100644
index 0000000..0bb9b7a
--- /dev/null
+++ b/paging/paging-guava/api/3.1.0-beta02.txt
@@ -0,0 +1,29 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class AdjacentItems<T> {
+    ctor public AdjacentItems(T? before, T? after);
+    method public T? component1();
+    method public T? component2();
+    method public androidx.paging.AdjacentItems<T> copy(T? before, T? after);
+    method public T? getAfter();
+    method public T? getBefore();
+    property public final T? after;
+    property public final T? before;
+  }
+
+  public abstract class ListenableFuturePagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public ListenableFuturePagingSource();
+    method public suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagingSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  public final class PagingDataFutures {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Boolean> predicate, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Iterable<R>> transform, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<androidx.paging.AdjacentItems<T>,R> generator, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,R> transform, java.util.concurrent.Executor executor);
+  }
+
+}
+
diff --git a/paging/paging-guava/api/public_plus_experimental_3.1.0-beta02.txt b/paging/paging-guava/api/public_plus_experimental_3.1.0-beta02.txt
new file mode 100644
index 0000000..381ebbb4
--- /dev/null
+++ b/paging/paging-guava/api/public_plus_experimental_3.1.0-beta02.txt
@@ -0,0 +1,37 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class AdjacentItems<T> {
+    ctor public AdjacentItems(T? before, T? after);
+    method public T? component1();
+    method public T? component2();
+    method public androidx.paging.AdjacentItems<T> copy(T? before, T? after);
+    method public T? getAfter();
+    method public T? getBefore();
+    property public final T? after;
+    property public final T? before;
+  }
+
+  public abstract class ListenableFuturePagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public ListenableFuturePagingSource();
+    method public suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagingSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @androidx.paging.ExperimentalPagingApi public abstract class ListenableFutureRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public ListenableFutureRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.RemoteMediator.InitializeAction> initializeFuture();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.RemoteMediator.MediatorResult> loadFuture(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+  public final class PagingDataFutures {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Boolean> predicate, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Iterable<R>> transform, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<androidx.paging.AdjacentItems<T>,R> generator, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,R> transform, java.util.concurrent.Executor executor);
+  }
+
+}
+
diff --git a/paging/paging-guava/api/res-3.1.0-beta02.txt b/paging/paging-guava/api/res-3.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-guava/api/res-3.1.0-beta02.txt
diff --git a/paging/paging-guava/api/restricted_3.1.0-beta02.txt b/paging/paging-guava/api/restricted_3.1.0-beta02.txt
new file mode 100644
index 0000000..0bb9b7a
--- /dev/null
+++ b/paging/paging-guava/api/restricted_3.1.0-beta02.txt
@@ -0,0 +1,29 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class AdjacentItems<T> {
+    ctor public AdjacentItems(T? before, T? after);
+    method public T? component1();
+    method public T? component2();
+    method public androidx.paging.AdjacentItems<T> copy(T? before, T? after);
+    method public T? getAfter();
+    method public T? getBefore();
+    property public final T? after;
+    property public final T? before;
+  }
+
+  public abstract class ListenableFuturePagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public ListenableFuturePagingSource();
+    method public suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagingSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  public final class PagingDataFutures {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Boolean> predicate, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Iterable<R>> transform, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<androidx.paging.AdjacentItems<T>,R> generator, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,R> transform, java.util.concurrent.Executor executor);
+  }
+
+}
+
diff --git a/paging/paging-runtime-ktx/api/3.1.0-beta02.txt b/paging/paging-runtime-ktx/api/3.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-runtime-ktx/api/3.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-runtime-ktx/api/public_plus_experimental_3.1.0-beta02.txt b/paging/paging-runtime-ktx/api/public_plus_experimental_3.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-runtime-ktx/api/public_plus_experimental_3.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-runtime-ktx/api/res-3.1.0-beta02.txt b/paging/paging-runtime-ktx/api/res-3.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-runtime-ktx/api/res-3.1.0-beta02.txt
diff --git a/paging/paging-runtime-ktx/api/restricted_3.1.0-beta02.txt b/paging/paging-runtime-ktx/api/restricted_3.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-runtime-ktx/api/restricted_3.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-runtime/api/3.1.0-beta02.txt b/paging/paging-runtime/api/3.1.0-beta02.txt
new file mode 100644
index 0000000..4fcb026
--- /dev/null
+++ b/paging/paging-runtime/api/3.1.0-beta02.txt
@@ -0,0 +1,139 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public class AsyncPagedListDiffer<T> {
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated public T? getItem(int index);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    property public androidx.paging.PagedList<T>? currentList;
+    property public int itemCount;
+  }
+
+  @Deprecated public static interface AsyncPagedListDiffer.PagedListListener<T> {
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+  }
+
+  public final class AsyncPagingDataDiffer<T> {
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public T? getItem(@IntRange(from=0) int index);
+    method public int getItemCount();
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method public T? peek(@IntRange(from=0) int index);
+    method public void refresh();
+    method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public void retry();
+    method public androidx.paging.ItemSnapshotList<T> snapshot();
+    method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    property public final int itemCount;
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+  }
+
+  @Deprecated public final class LivePagedListBuilder<Key, Value> {
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+  }
+
+  public final class LivePagedListKt {
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+  }
+
+  public abstract class LoadStateAdapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public LoadStateAdapter();
+    method public boolean displayLoadStateAsItem(androidx.paging.LoadState loadState);
+    method public final int getItemCount();
+    method public final int getItemViewType(int position);
+    method public final androidx.paging.LoadState getLoadState();
+    method public int getStateViewType(androidx.paging.LoadState loadState);
+    method public final void onBindViewHolder(VH holder, int position);
+    method public abstract void onBindViewHolder(VH holder, androidx.paging.LoadState loadState);
+    method public final VH onCreateViewHolder(android.view.ViewGroup parent, int viewType);
+    method public abstract VH onCreateViewHolder(android.view.ViewGroup parent, androidx.paging.LoadState loadState);
+    method public final void setLoadState(androidx.paging.LoadState loadState);
+    property public final androidx.paging.LoadState loadState;
+  }
+
+  public final class NullPaddedListDiffHelperKt {
+  }
+
+  @Deprecated public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated protected T? getItem(int position);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public androidx.paging.PagedList<T>? currentList;
+  }
+
+  public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method protected final T? getItem(@IntRange(from=0) int position);
+    method public int getItemCount();
+    method public final long getItemId(int position);
+    method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method public final T? peek(@IntRange(from=0) int index);
+    method public final void refresh();
+    method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public final void retry();
+    method public final void setHasStableIds(boolean hasStableIds);
+    method public final androidx.paging.ItemSnapshotList<T> snapshot();
+    method public final suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public final void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+  }
+
+  public final class PagingLiveData {
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.Lifecycle lifecycle);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.ViewModel viewModel);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagingData<Value>> getLiveData(androidx.paging.Pager<Key,Value>);
+  }
+
+}
+
diff --git a/paging/paging-runtime/api/public_plus_experimental_3.1.0-beta02.txt b/paging/paging-runtime/api/public_plus_experimental_3.1.0-beta02.txt
new file mode 100644
index 0000000..4fcb026
--- /dev/null
+++ b/paging/paging-runtime/api/public_plus_experimental_3.1.0-beta02.txt
@@ -0,0 +1,139 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public class AsyncPagedListDiffer<T> {
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated public T? getItem(int index);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    property public androidx.paging.PagedList<T>? currentList;
+    property public int itemCount;
+  }
+
+  @Deprecated public static interface AsyncPagedListDiffer.PagedListListener<T> {
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+  }
+
+  public final class AsyncPagingDataDiffer<T> {
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public T? getItem(@IntRange(from=0) int index);
+    method public int getItemCount();
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method public T? peek(@IntRange(from=0) int index);
+    method public void refresh();
+    method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public void retry();
+    method public androidx.paging.ItemSnapshotList<T> snapshot();
+    method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    property public final int itemCount;
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+  }
+
+  @Deprecated public final class LivePagedListBuilder<Key, Value> {
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+  }
+
+  public final class LivePagedListKt {
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+  }
+
+  public abstract class LoadStateAdapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public LoadStateAdapter();
+    method public boolean displayLoadStateAsItem(androidx.paging.LoadState loadState);
+    method public final int getItemCount();
+    method public final int getItemViewType(int position);
+    method public final androidx.paging.LoadState getLoadState();
+    method public int getStateViewType(androidx.paging.LoadState loadState);
+    method public final void onBindViewHolder(VH holder, int position);
+    method public abstract void onBindViewHolder(VH holder, androidx.paging.LoadState loadState);
+    method public final VH onCreateViewHolder(android.view.ViewGroup parent, int viewType);
+    method public abstract VH onCreateViewHolder(android.view.ViewGroup parent, androidx.paging.LoadState loadState);
+    method public final void setLoadState(androidx.paging.LoadState loadState);
+    property public final androidx.paging.LoadState loadState;
+  }
+
+  public final class NullPaddedListDiffHelperKt {
+  }
+
+  @Deprecated public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated protected T? getItem(int position);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public androidx.paging.PagedList<T>? currentList;
+  }
+
+  public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method protected final T? getItem(@IntRange(from=0) int position);
+    method public int getItemCount();
+    method public final long getItemId(int position);
+    method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method public final T? peek(@IntRange(from=0) int index);
+    method public final void refresh();
+    method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public final void retry();
+    method public final void setHasStableIds(boolean hasStableIds);
+    method public final androidx.paging.ItemSnapshotList<T> snapshot();
+    method public final suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public final void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+  }
+
+  public final class PagingLiveData {
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.Lifecycle lifecycle);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.ViewModel viewModel);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagingData<Value>> getLiveData(androidx.paging.Pager<Key,Value>);
+  }
+
+}
+
diff --git a/paging/paging-runtime/api/res-3.1.0-beta02.txt b/paging/paging-runtime/api/res-3.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-runtime/api/res-3.1.0-beta02.txt
diff --git a/paging/paging-runtime/api/restricted_3.1.0-beta02.txt b/paging/paging-runtime/api/restricted_3.1.0-beta02.txt
new file mode 100644
index 0000000..4fcb026
--- /dev/null
+++ b/paging/paging-runtime/api/restricted_3.1.0-beta02.txt
@@ -0,0 +1,139 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public class AsyncPagedListDiffer<T> {
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated public T? getItem(int index);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    property public androidx.paging.PagedList<T>? currentList;
+    property public int itemCount;
+  }
+
+  @Deprecated public static interface AsyncPagedListDiffer.PagedListListener<T> {
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+  }
+
+  public final class AsyncPagingDataDiffer<T> {
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public T? getItem(@IntRange(from=0) int index);
+    method public int getItemCount();
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method public T? peek(@IntRange(from=0) int index);
+    method public void refresh();
+    method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public void retry();
+    method public androidx.paging.ItemSnapshotList<T> snapshot();
+    method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    property public final int itemCount;
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+  }
+
+  @Deprecated public final class LivePagedListBuilder<Key, Value> {
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+  }
+
+  public final class LivePagedListKt {
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+  }
+
+  public abstract class LoadStateAdapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public LoadStateAdapter();
+    method public boolean displayLoadStateAsItem(androidx.paging.LoadState loadState);
+    method public final int getItemCount();
+    method public final int getItemViewType(int position);
+    method public final androidx.paging.LoadState getLoadState();
+    method public int getStateViewType(androidx.paging.LoadState loadState);
+    method public final void onBindViewHolder(VH holder, int position);
+    method public abstract void onBindViewHolder(VH holder, androidx.paging.LoadState loadState);
+    method public final VH onCreateViewHolder(android.view.ViewGroup parent, int viewType);
+    method public abstract VH onCreateViewHolder(android.view.ViewGroup parent, androidx.paging.LoadState loadState);
+    method public final void setLoadState(androidx.paging.LoadState loadState);
+    property public final androidx.paging.LoadState loadState;
+  }
+
+  public final class NullPaddedListDiffHelperKt {
+  }
+
+  @Deprecated public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated protected T? getItem(int position);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public androidx.paging.PagedList<T>? currentList;
+  }
+
+  public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method protected final T? getItem(@IntRange(from=0) int position);
+    method public int getItemCount();
+    method public final long getItemId(int position);
+    method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method public final T? peek(@IntRange(from=0) int index);
+    method public final void refresh();
+    method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public final void retry();
+    method public final void setHasStableIds(boolean hasStableIds);
+    method public final androidx.paging.ItemSnapshotList<T> snapshot();
+    method public final suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public final void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+  }
+
+  public final class PagingLiveData {
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.Lifecycle lifecycle);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.ViewModel viewModel);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagingData<Value>> getLiveData(androidx.paging.Pager<Key,Value>);
+  }
+
+}
+
diff --git a/paging/paging-rxjava2-ktx/api/3.1.0-beta02.txt b/paging/paging-rxjava2-ktx/api/3.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-rxjava2-ktx/api/3.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-rxjava2-ktx/api/public_plus_experimental_3.1.0-beta02.txt b/paging/paging-rxjava2-ktx/api/public_plus_experimental_3.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-rxjava2-ktx/api/public_plus_experimental_3.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-rxjava2-ktx/api/res-3.1.0-beta02.txt b/paging/paging-rxjava2-ktx/api/res-3.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-rxjava2-ktx/api/res-3.1.0-beta02.txt
diff --git a/paging/paging-rxjava2-ktx/api/restricted_3.1.0-beta02.txt b/paging/paging-rxjava2-ktx/api/restricted_3.1.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-rxjava2-ktx/api/restricted_3.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-rxjava2/api/3.1.0-beta02.txt b/paging/paging-rxjava2/api/3.1.0-beta02.txt
new file mode 100644
index 0000000..afe197e
--- /dev/null
+++ b/paging/paging-rxjava2/api/3.1.0-beta02.txt
@@ -0,0 +1,57 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.Scheduler scheduler);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+  }
+
+}
+
+package androidx.paging.rxjava2 {
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/paging-rxjava2/api/public_plus_experimental_3.1.0-beta02.txt b/paging/paging-rxjava2/api/public_plus_experimental_3.1.0-beta02.txt
new file mode 100644
index 0000000..89bd6c6
--- /dev/null
+++ b/paging/paging-rxjava2/api/public_plus_experimental_3.1.0-beta02.txt
@@ -0,0 +1,69 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.Scheduler scheduler);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+  }
+
+}
+
+package androidx.paging.rxjava2 {
+
+  public final class PagingRx {
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public RxRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+    method public io.reactivex.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+    method public abstract io.reactivex.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+}
+
diff --git a/paging/paging-rxjava2/api/res-3.1.0-beta02.txt b/paging/paging-rxjava2/api/res-3.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-rxjava2/api/res-3.1.0-beta02.txt
diff --git a/paging/paging-rxjava2/api/restricted_3.1.0-beta02.txt b/paging/paging-rxjava2/api/restricted_3.1.0-beta02.txt
new file mode 100644
index 0000000..afe197e
--- /dev/null
+++ b/paging/paging-rxjava2/api/restricted_3.1.0-beta02.txt
@@ -0,0 +1,57 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.Scheduler scheduler);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+  }
+
+}
+
+package androidx.paging.rxjava2 {
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/paging-rxjava3/api/3.1.0-beta02.txt b/paging/paging-rxjava3/api/3.1.0-beta02.txt
new file mode 100644
index 0000000..304fe98
--- /dev/null
+++ b/paging/paging-rxjava3/api/3.1.0-beta02.txt
@@ -0,0 +1,53 @@
+// Signature format: 4.0
+package androidx.paging.rxjava3 {
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.rxjava3.core.Scheduler scheduler);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.rxjava3.core.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/paging-rxjava3/api/public_plus_experimental_3.1.0-beta02.txt b/paging/paging-rxjava3/api/public_plus_experimental_3.1.0-beta02.txt
new file mode 100644
index 0000000..f847113
--- /dev/null
+++ b/paging/paging-rxjava3/api/public_plus_experimental_3.1.0-beta02.txt
@@ -0,0 +1,65 @@
+// Signature format: 4.0
+package androidx.paging.rxjava3 {
+
+  public final class PagingRx {
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.rxjava3.core.Scheduler scheduler);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.rxjava3.core.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public RxRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+    method public io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+}
+
diff --git a/paging/paging-rxjava3/api/res-3.1.0-beta02.txt b/paging/paging-rxjava3/api/res-3.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-rxjava3/api/res-3.1.0-beta02.txt
diff --git a/paging/paging-rxjava3/api/restricted_3.1.0-beta02.txt b/paging/paging-rxjava3/api/restricted_3.1.0-beta02.txt
new file mode 100644
index 0000000..304fe98
--- /dev/null
+++ b/paging/paging-rxjava3/api/restricted_3.1.0-beta02.txt
@@ -0,0 +1,53 @@
+// Signature format: 4.0
+package androidx.paging.rxjava3 {
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.rxjava3.core.Scheduler scheduler);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.rxjava3.core.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAccessibilityTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAccessibilityTest.java
index a414569..8551053 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAccessibilityTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewAccessibilityTest.java
@@ -24,8 +24,12 @@
 
 import android.os.Build;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.Interpolator;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.Px;
 import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.test.filters.SdkSuppress;
@@ -38,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 @SmallTest
 @RunWith(Parameterized.class)
@@ -192,7 +197,7 @@
         hScrolledBack.set(false);
         vScrolledBack.set(false);
         hScrolledFwd.set(false);
-        vScrolledBack.set(false);
+        vScrolledFwd.set(false);
         performAccessibilityAction(delegateCompat, recyclerView,
                 AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
         assertEquals(mHorizontalScrollBefore, hScrolledBack.get());
@@ -203,7 +208,7 @@
         hScrolledBack.set(false);
         vScrolledBack.set(false);
         hScrolledFwd.set(false);
-        vScrolledBack.set(false);
+        vScrolledFwd.set(false);
         performAccessibilityAction(delegateCompat, recyclerView,
                 AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
         assertEquals(false, hScrolledBack.get());
@@ -345,6 +350,125 @@
         checkForMainThreadException();
     }
 
+    @Test
+    public void performAction_scrollAction_rangeInVisibleRect() throws Throwable {
+        AtomicInteger hScrolledOffset = new AtomicInteger(0);
+        AtomicInteger vScrolledOffset = new AtomicInteger(0);
+
+        final RecyclerView recyclerView = new RecyclerView(getActivity()) {
+            @Override
+            void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator,
+                    int duration, boolean withNestedScrolling) {
+                // Overrides duration to 0 to stop segmentation to get the complete scroll distance.
+                int overrideDuration = 0;
+                super.smoothScrollBy(dx, dy, interpolator, overrideDuration, withNestedScrolling);
+            }
+
+            @Override
+            public boolean canScrollHorizontally(int direction) {
+                return true;
+            }
+
+            @Override
+            public boolean canScrollVertically(int direction) {
+                return true;
+            }
+        };
+        final TestAdapter adapter = new TestAdapter(10);
+
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                layoutRange(recycler, 0, 5);
+            }
+
+            @Override
+            public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+                return new RecyclerView.LayoutParams(-1, -1);
+            }
+
+            @Override
+            public boolean canScrollVertically() {
+                return true;
+            }
+
+            @Override
+            public boolean canScrollHorizontally() {
+                return true;
+            }
+
+            @Override
+            public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                hScrolledOffset.set(dx);
+                return 0;
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                vScrolledOffset.set(dy);
+                return 0;
+            }
+        });
+        setRecyclerView(recyclerView);
+        final RecyclerViewAccessibilityDelegate delegateCompat = recyclerView
+                .getCompatAccessibilityDelegate();
+        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(recyclerView, info);
+            }
+        });
+        getInstrumentation().waitForIdleSync();
+
+        assertEquals(true, info.isScrollable());
+        assertEquals(true,
+                (info.getActions() & AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD) != 0);
+        assertEquals(true,
+                (info.getActions() & AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD) != 0);
+
+        int width = recyclerView.getWidth();
+        int height = recyclerView.getHeight();
+        performAccessibilityAction(delegateCompat, recyclerView,
+                AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
+        assertEquals(-width, hScrolledOffset.get());
+        assertEquals(-height, vScrolledOffset.get());
+
+        performAccessibilityAction(delegateCompat, recyclerView,
+                AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
+        assertEquals(width, hScrolledOffset.get());
+        assertEquals(height, vScrolledOffset.get());
+
+        final ViewGroup parent = getRecyclerViewContainer();
+        final ViewGroup.LayoutParams originalLayoutParams = parent.getLayoutParams();
+        try {
+            // Sets RecyclerView's parent to half size to limit the visible rect of RecyclerView.
+            final int halfWidth = width / 2;
+            final int halfHeight = height / 2;
+            final TestedFrameLayout.FullControlLayoutParams halfSizeLayoutParams =
+                    new TestedFrameLayout.FullControlLayoutParams(halfWidth, halfHeight);
+            mActivityRule.runOnUiThread(() -> parent.setLayoutParams(halfSizeLayoutParams));
+            getInstrumentation().waitForIdleSync();
+
+            performAccessibilityAction(delegateCompat, recyclerView,
+                    AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
+            assertEquals(-halfWidth, hScrolledOffset.get());
+            assertEquals(-halfHeight, vScrolledOffset.get());
+
+            performAccessibilityAction(delegateCompat, recyclerView,
+                    AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
+            assertEquals(halfWidth, hScrolledOffset.get());
+            assertEquals(halfHeight, vScrolledOffset.get());
+        } finally {
+            // Sets RecyclerView's parent to original size.
+            mActivityRule.runOnUiThread(() -> parent.setLayoutParams(originalLayoutParams));
+            getInstrumentation().waitForIdleSync();
+        }
+    }
+
     boolean performAccessibilityAction(final AccessibilityDelegateCompat delegate,
             final RecyclerView recyclerView, final int action) throws Throwable {
         final boolean[] result = new boolean[1];
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index 717d05f..26dfbe52 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -10986,21 +10986,31 @@
                 return false;
             }
             int vScroll = 0, hScroll = 0;
+            int height = getHeight();
+            int width = getWidth();
+            Rect rect = new Rect();
+            // Gets the visible rect on the screen except for the rotation or scale cases which
+            // might affect the result.
+            if (mRecyclerView.getMatrix().isIdentity() && mRecyclerView.getGlobalVisibleRect(
+                    rect)) {
+                height = rect.height();
+                width = rect.width();
+            }
             switch (action) {
                 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
                     if (mRecyclerView.canScrollVertically(-1)) {
-                        vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom());
+                        vScroll = -(height - getPaddingTop() - getPaddingBottom());
                     }
                     if (mRecyclerView.canScrollHorizontally(-1)) {
-                        hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight());
+                        hScroll = -(width - getPaddingLeft() - getPaddingRight());
                     }
                     break;
                 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
                     if (mRecyclerView.canScrollVertically(1)) {
-                        vScroll = getHeight() - getPaddingTop() - getPaddingBottom();
+                        vScroll = height - getPaddingTop() - getPaddingBottom();
                     }
                     if (mRecyclerView.canScrollHorizontally(1)) {
-                        hScroll = getWidth() - getPaddingLeft() - getPaddingRight();
+                        hScroll = width - getPaddingLeft() - getPaddingRight();
                     }
                     break;
             }
diff --git a/room/integration-tests/kotlintestapp/src/androidTestWithKsp/java/androidx/room/integration/kotlintestapp/NullabilityAwareTypeConversionTest.kt b/room/integration-tests/kotlintestapp/src/androidTestWithKsp/java/androidx/room/integration/kotlintestapp/NullabilityAwareTypeConversionTest.kt
new file mode 100644
index 0000000..85c86ea
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTestWithKsp/java/androidx/room/integration/kotlintestapp/NullabilityAwareTypeConversionTest.kt
@@ -0,0 +1,357 @@
+/*
+ * 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.room.integration.kotlintestapp
+
+import android.database.Cursor
+import androidx.room.Dao
+import androidx.room.Database
+import androidx.room.Entity
+import androidx.room.Insert
+import androidx.room.PrimaryKey
+import androidx.room.ProvidedTypeConverter
+import androidx.room.Query
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverter
+import androidx.room.TypeConverters
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This test can only pass in KSP with the new type converter store, which is why it is only in the
+ * KSP specific source set.
+ */
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NullabilityAwareTypeConversionTest {
+    lateinit var dao: UserDao
+    private val nullableConvertors = NullableTypeConverters()
+
+    @Before
+    fun init() {
+        dao = Room.inMemoryDatabaseBuilder(
+            ApplicationProvider.getApplicationContext(),
+            NullAwareConverterDatabase::class.java
+        ).addTypeConverter(nullableConvertors).build().userDao
+    }
+
+    private fun assertNullableConverterIsNotUsed() {
+        assertWithMessage(
+            "should've not used nullable conversion since it is not available in this scope"
+        ).that(nullableConvertors.toStringInvocations).isEmpty()
+        assertWithMessage(
+            "should've not used nullable conversion since it is not available in this scope"
+        ).that(nullableConvertors.fromStringInvocations).isEmpty()
+    }
+
+    @Test
+    fun insert() {
+        val user = User(
+            id = 1,
+            nonNullCountry = Country.FRANCE,
+            nullableCountry = Country.UNITED_KINGDOM
+        )
+        dao.insert(user)
+        assertThat(
+            dao.getRawData()
+        ).isEqualTo("1-FR-UK")
+        assertNullableConverterIsNotUsed()
+    }
+
+    @Test
+    fun setNonNullColumn() {
+        val user = User(
+            id = 1,
+            nonNullCountry = Country.FRANCE,
+            nullableCountry = null
+        )
+        dao.insert(user)
+        assertThat(
+            dao.getRawData()
+        ).isEqualTo("1-FR-null")
+        dao.setNonNullCountry(id = 1, nonNullCountry = Country.UNITED_KINGDOM)
+        assertThat(
+            dao.getRawData()
+        ).isEqualTo("1-UK-null")
+        assertNullableConverterIsNotUsed()
+    }
+
+    @Test
+    fun setNullableColumn() {
+        val user = User(
+            id = 1,
+            nonNullCountry = Country.FRANCE,
+            nullableCountry = Country.UNITED_KINGDOM
+        )
+        dao.insert(user)
+        dao.setNullableCountry(id = 1, nullableCountry = null)
+        assertThat(
+            dao.getRawData()
+        ).isEqualTo("1-FR-null")
+        dao.setNullableCountry(id = 1, nullableCountry = Country.UNITED_KINGDOM)
+        assertThat(
+            dao.getRawData()
+        ).isEqualTo("1-FR-UK")
+        assertNullableConverterIsNotUsed()
+    }
+
+    @Test
+    fun load() {
+        val user1 = User(
+            id = 1,
+            nonNullCountry = Country.FRANCE,
+            nullableCountry = Country.UNITED_KINGDOM
+        )
+        dao.insert(user1)
+        assertThat(
+            dao.getById(1)
+        ).isEqualTo(user1)
+        val user2 = User(
+            id = 2,
+            nonNullCountry = Country.UNITED_KINGDOM,
+            nullableCountry = null
+        )
+        dao.insert(user2)
+        assertThat(
+            dao.getById(2)
+        ).isEqualTo(user2)
+        assertNullableConverterIsNotUsed()
+    }
+
+    @Test
+    fun useNullableConverter() {
+        val user = User(
+            id = 1,
+            nonNullCountry = Country.FRANCE,
+            nullableCountry = Country.UNITED_KINGDOM
+        )
+        dao.insert(user)
+        dao.setNullableCountryWithNullableTypeConverter(
+            id = 1,
+            nullableCountry = null
+        )
+        assertThat(
+            dao.getRawData()
+        ).isEqualTo("1-FR-null")
+        assertThat(
+            nullableConvertors.toStringInvocations
+        ).containsExactly(null)
+    }
+
+    @Test
+    fun loadNonNullColumn() {
+        val user = User(
+            id = 1,
+            nonNullCountry = Country.FRANCE,
+            nullableCountry = null
+        )
+        dao.insert(user)
+        val country = dao.getNonNullCountry(id = 1)
+        assertThat(country).isEqualTo(Country.FRANCE)
+        assertNullableConverterIsNotUsed()
+    }
+
+    @Test
+    fun loadNullableColumn() {
+        val user = User(
+            id = 1,
+            nonNullCountry = Country.FRANCE,
+            nullableCountry = null
+        )
+        dao.insert(user)
+        val country = dao.getNullableCountry(id = 1)
+        assertThat(country).isNull()
+        assertNullableConverterIsNotUsed()
+    }
+
+    @Test
+    fun loadNonNullColumn_withNullableConverter() {
+        val user = User(
+            id = 1,
+            nonNullCountry = Country.FRANCE,
+            nullableCountry = null
+        )
+        dao.insert(user)
+        val country = dao.getNonNullCountryWithNullableTypeConverter(id = 1)
+        assertThat(country).isEqualTo(Country.FRANCE)
+        // return value is non-null so it is better to use non-null converter and assume
+        // column is non-null, instead of using the nullable converter
+        assertNullableConverterIsNotUsed()
+    }
+
+    @Test
+    fun loadNonNullColumn_asNullable_withNullableConverter() {
+        val user = User(
+            id = 1,
+            nonNullCountry = Country.FRANCE,
+            nullableCountry = null
+        )
+        dao.insert(user)
+        val country = dao.getNonNullCountryAsNullableWithNullableTypeConverter(id = 1)
+        assertThat(country).isEqualTo(Country.FRANCE)
+        // return value is nullable so we are using the nullable converter because room does not
+        // know that the column is non-null.
+        // if one day Room understands it and this test fails, feel free to update it.
+        // We still want this test because right now Room does not know column is non-null hence
+        // it should prefer the nullable converter.
+        assertThat(
+            nullableConvertors.fromStringInvocations
+        ).containsExactly("FR")
+    }
+
+    @Test
+    fun loadNullableColumn_withNullableConverter() {
+        val user = User(
+            id = 1,
+            nonNullCountry = Country.FRANCE,
+            nullableCountry = null
+        )
+        dao.insert(user)
+        val country = dao.getNullableCountryWithNullableTypeConverter(id = 1)
+        assertThat(country).isNull()
+        assertThat(
+            nullableConvertors.fromStringInvocations
+        ).containsExactly(null)
+    }
+
+    @Database(
+        version = 1,
+        entities = [
+            User::class,
+        ],
+        exportSchema = false
+    )
+    @TypeConverters(NonNullTypeConverters::class)
+    abstract class NullAwareConverterDatabase : RoomDatabase() {
+        abstract val userDao: UserDao
+    }
+
+    @Dao
+    abstract class UserDao {
+
+        @Insert
+        abstract fun insert(user: User): Long
+
+        @Query("UPDATE user SET nonNullCountry = :nonNullCountry WHERE id = :id")
+        abstract fun setNonNullCountry(id: Long, nonNullCountry: Country)
+
+        @Query("UPDATE user SET nullableCountry = :nullableCountry WHERE id = :id")
+        abstract fun setNullableCountry(id: Long, nullableCountry: Country?)
+
+        @Query("SELECT * FROM user WHERE id = :id")
+        abstract fun getById(id: Long): User?
+
+        @Query("UPDATE user SET nullableCountry = :nullableCountry WHERE id = :id")
+        @TypeConverters(NullableTypeConverters::class)
+        abstract fun setNullableCountryWithNullableTypeConverter(
+            id: Long,
+            nullableCountry: Country?
+        )
+
+        @Query("SELECT nonNullCountry FROM user WHERE id = :id")
+        abstract fun getNonNullCountry(id: Long): Country
+
+        @Query("SELECT nullableCountry FROM user WHERE id = :id")
+        abstract fun getNullableCountry(id: Long): Country?
+
+        @Query("SELECT nullableCountry FROM user WHERE id = :id")
+        @TypeConverters(NullableTypeConverters::class)
+        abstract fun getNullableCountryWithNullableTypeConverter(id: Long): Country?
+
+        @Query("SELECT nonNullCountry FROM user WHERE id = :id")
+        @TypeConverters(NullableTypeConverters::class)
+        abstract fun getNonNullCountryWithNullableTypeConverter(id: Long): Country
+
+        @Query("SELECT nonNullCountry FROM user WHERE id = :id")
+        @TypeConverters(NullableTypeConverters::class)
+        abstract fun getNonNullCountryAsNullableWithNullableTypeConverter(id: Long): Country?
+
+        @Query("SELECT * FROM User ORDER BY id")
+        protected abstract fun getUsers(): Cursor
+
+        /**
+         * Return raw data in the database so that we can assert what is in the database
+         * without room's converters
+         */
+        fun getRawData(): String {
+            return buildString {
+                getUsers().use {
+                    if (it.moveToNext()) {
+                        append(it.getInt(0))
+                        append("-")
+                        append(it.getString(1))
+                        append("-")
+                        append(it.getString(2))
+                    }
+                }
+            }
+        }
+    }
+
+    @Entity(tableName = "user")
+    data class User(
+        @PrimaryKey
+        val id: Long,
+        val nonNullCountry: Country,
+        val nullableCountry: Country?,
+    )
+
+    enum class Country(val countryCode: String) {
+        UNITED_KINGDOM("UK"),
+        FRANCE("FR"),
+    }
+
+    object NonNullTypeConverters {
+        @TypeConverter
+        fun toString(country: Country): String {
+            return country.countryCode
+        }
+
+        @TypeConverter
+        fun toCountry(string: String): Country {
+            return Country.values().find { it.countryCode == string }
+                ?: throw IllegalArgumentException("Country code '$string' not found")
+        }
+    }
+
+    @ProvidedTypeConverter
+    class NullableTypeConverters {
+        val toStringInvocations = mutableListOf<Country?>()
+        val fromStringInvocations = mutableListOf<String?>()
+        @TypeConverter
+        fun toString(country: Country?): String? {
+            toStringInvocations.add(country)
+            return country?.countryCode
+        }
+
+        @TypeConverter
+        fun toCountry(string: String?): Country? {
+            fromStringInvocations.add(string)
+            if (string == null) {
+                return null
+            }
+            return Country.values().find { it.countryCode == string }
+                ?: throw IllegalArgumentException("Country code '$string' not found")
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index 17cf821..358a6e5 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -68,6 +68,18 @@
                 }
             }
         }
+        withNullAwareTypeConverter {
+            dimension "processorConfiguration"
+            javaCompileOptions {
+                annotationProcessorOptions {
+                    arguments = [
+                            "room.schemaLocation"  : "$projectDir/schemas".toString(),
+                            "room.expandProjection"  : "false",
+                            "room.useNullAwareTypeAnalysis": "true"
+                    ]
+                }
+            }
+        }
     }
 }
 
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java
index 713b8c9..7cadc72 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/MusicDao.java
@@ -17,6 +17,8 @@
 package androidx.room.integration.testapp.dao;
 
 import androidx.collection.ArrayMap;
+import androidx.collection.LongSparseArray;
+import androidx.collection.SparseArrayCompat;
 import androidx.lifecycle.LiveData;
 import androidx.room.Dao;
 import androidx.room.Insert;
@@ -206,6 +208,16 @@
     Map<Integer, List<Song>> getReleaseYearToAlbums();
 
     @RewriteQueriesToDropUnusedColumns
+    @MapInfo(keyColumn = "mArtistId")
+    @Query("SELECT * FROM Artist JOIN Image ON Artist.mArtistName = Image.mArtistInImage")
+    LongSparseArray<Artist> getAllAlbumCoverYearToArtistsWithLongSparseArray();
+
+    @RewriteQueriesToDropUnusedColumns
+    @MapInfo(keyColumn = "mArtistId")
+    @Query("SELECT * FROM Artist JOIN Image ON Artist.mArtistName = Image.mArtistInImage")
+    SparseArrayCompat<Artist> getAllAlbumCoverYearToArtistsWithIntSparseArray();
+
+    @RewriteQueriesToDropUnusedColumns
     @MapInfo(keyColumn = "mReleasedYear", valueColumn = "mTitle")
     @Query("SELECT * FROM Album JOIN Song ON Song.mReleasedYear = Album.mAlbumReleaseYear")
     Map<Integer, List<String>> getReleaseYearToSongNames();
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultimapQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultimapQueryTest.java
index ab71b1c..85c737c 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultimapQueryTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultimapQueryTest.java
@@ -25,6 +25,8 @@
 
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
 import androidx.collection.ArrayMap;
+import androidx.collection.LongSparseArray;
+import androidx.collection.SparseArrayCompat;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.testing.TestLifecycleOwner;
@@ -936,6 +938,31 @@
     }
 
     @Test
+    public void testImageYearToArtistLongSparseArray() {
+        mMusicDao.addArtists(mRhcp, mAcDc, mTheClash, mPinkFloyd);
+        mMusicDao.addImages(mPinkFloydAlbumCover, mRhcpAlbumCover);
+
+        LongSparseArray<Artist> imageToArtistsMap =
+                mMusicDao.getAllAlbumCoverYearToArtistsWithLongSparseArray();
+
+        assertThat(imageToArtistsMap.size()).isEqualTo(2);
+        assertThat(imageToArtistsMap.get(1)).isEqualTo(mRhcp);
+    }
+
+    @Test
+    public void testImageYearToArtistSparseArrayCompat() {
+        mMusicDao.addArtists(mRhcp, mAcDc, mTheClash, mPinkFloyd);
+        mMusicDao.addImages(mPinkFloydAlbumCover, mRhcpAlbumCover);
+
+        SparseArrayCompat<Artist> imageToArtistsMap =
+                mMusicDao.getAllAlbumCoverYearToArtistsWithIntSparseArray();
+
+        assertThat(imageToArtistsMap.size()).isEqualTo(2);
+        assertThat(imageToArtistsMap.get(1)).isEqualTo(mRhcp);
+        assertThat(imageToArtistsMap.get(4)).isEqualTo(mPinkFloyd);
+    }
+
+    @Test
     public void testImageYearToArtistRawQueryArrayMap() {
         mMusicDao.addArtists(mRhcp, mAcDc, mTheClash, mPinkFloyd);
         mMusicDao.addImages(mPinkFloydAlbumCover, mRhcpAlbumCover);
diff --git a/room/room-common/api/2.4.0-beta02.txt b/room/room-common/api/2.4.0-beta02.txt
new file mode 100644
index 0000000..74796f1
--- /dev/null
+++ b/room/room-common/api/2.4.0-beta02.txt
@@ -0,0 +1,276 @@
+// Signature format: 4.0
+package androidx.room {
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface AutoMigration {
+    method public abstract int from();
+    method public abstract Class<?> spec() default java.lang.Object.class;
+    method public abstract int to();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface BuiltInTypeConverters {
+    method public abstract androidx.room.BuiltInTypeConverters.State enums() default androidx.room.BuiltInTypeConverters.State.INHERITED;
+    method public abstract androidx.room.BuiltInTypeConverters.State uuid() default androidx.room.BuiltInTypeConverters.State.INHERITED;
+  }
+
+  public enum BuiltInTypeConverters.State {
+    enum_constant public static final androidx.room.BuiltInTypeConverters.State DISABLED;
+    enum_constant public static final androidx.room.BuiltInTypeConverters.State ENABLED;
+    enum_constant public static final androidx.room.BuiltInTypeConverters.State INHERITED;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ColumnInfo {
+    method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+    method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+    method public abstract boolean index() default false;
+    method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+    method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+    field public static final int BINARY = 2; // 0x2
+    field public static final int BLOB = 5; // 0x5
+    field public static final String INHERIT_FIELD_NAME = "[field-name]";
+    field public static final int INTEGER = 3; // 0x3
+    field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+    field public static final int NOCASE = 3; // 0x3
+    field public static final int REAL = 4; // 0x4
+    field public static final int RTRIM = 4; // 0x4
+    field public static final int TEXT = 2; // 0x2
+    field public static final int UNDEFINED = 1; // 0x1
+    field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+    field public static final int UNSPECIFIED = 1; // 0x1
+    field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Dao {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Database {
+    method public abstract androidx.room.AutoMigration[] autoMigrations() default {};
+    method public abstract Class<?>[] entities();
+    method public abstract boolean exportSchema() default true;
+    method public abstract int version();
+    method public abstract Class<?>[] views() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DatabaseView {
+    method public abstract String value() default "";
+    method public abstract String viewName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Delete {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+  }
+
+  @java.lang.annotation.Repeatable(DeleteColumn.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DeleteColumn {
+    method public abstract String columnName();
+    method public abstract String tableName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface DeleteColumn.Entries {
+    method public abstract androidx.room.DeleteColumn[] value();
+  }
+
+  @java.lang.annotation.Repeatable(DeleteTable.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DeleteTable {
+    method public abstract String tableName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface DeleteTable.Entries {
+    method public abstract androidx.room.DeleteTable[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Embedded {
+    method public abstract String prefix() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Entity {
+    method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+    method public abstract String[] ignoredColumns() default {};
+    method public abstract androidx.room.Index[] indices() default {};
+    method public abstract boolean inheritSuperIndices() default false;
+    method public abstract String[] primaryKeys() default {};
+    method public abstract String tableName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface ForeignKey {
+    method public abstract String[] childColumns();
+    method public abstract boolean deferred() default false;
+    method public abstract Class<?> entity();
+    method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+    method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+    method public abstract String[] parentColumns();
+    field public static final int CASCADE = 5; // 0x5
+    field public static final int NO_ACTION = 1; // 0x1
+    field public static final int RESTRICT = 2; // 0x2
+    field public static final int SET_DEFAULT = 4; // 0x4
+    field public static final int SET_NULL = 3; // 0x3
+  }
+
+  @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+    method public abstract Class<?> contentEntity() default java.lang.Object.class;
+    method public abstract String languageId() default "";
+    method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+    method public abstract String[] notIndexed() default {};
+    method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+    method public abstract int[] prefix() default {};
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  public class FtsOptions {
+    field public static final String TOKENIZER_ICU = "icu";
+    field public static final String TOKENIZER_PORTER = "porter";
+    field public static final String TOKENIZER_SIMPLE = "simple";
+    field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+  }
+
+  public enum FtsOptions.MatchInfo {
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+  }
+
+  public enum FtsOptions.Order {
+    enum_constant public static final androidx.room.FtsOptions.Order ASC;
+    enum_constant public static final androidx.room.FtsOptions.Order DESC;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface Ignore {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
+    method public abstract String name() default "";
+    method public abstract androidx.room.Index.Order[] orders() default {};
+    method public abstract boolean unique() default false;
+    method public abstract String[] value();
+  }
+
+  public enum Index.Order {
+    enum_constant public static final androidx.room.Index.Order ASC;
+    enum_constant public static final androidx.room.Index.Order DESC;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Junction {
+    method public abstract String entityColumn() default "";
+    method public abstract String parentColumn() default "";
+    method public abstract Class<?> value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface MapInfo {
+    method public abstract String keyColumn() default "";
+    method public abstract String valueColumn() default "";
+  }
+
+  @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface OnConflictStrategy {
+    field public static final int ABORT = 3; // 0x3
+    field @Deprecated public static final int FAIL = 4; // 0x4
+    field public static final int IGNORE = 5; // 0x5
+    field public static final int REPLACE = 1; // 0x1
+    field @Deprecated public static final int ROLLBACK = 2; // 0x2
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface PrimaryKey {
+    method public abstract boolean autoGenerate() default false;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedAutoMigrationSpec {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Query {
+    method public abstract String value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface RawQuery {
+    method public abstract Class<?>[] observedEntities() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Relation {
+    method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method public abstract String entityColumn();
+    method public abstract String parentColumn();
+    method public abstract String[] projection() default {};
+  }
+
+  @java.lang.annotation.Repeatable(RenameColumn.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface RenameColumn {
+    method public abstract String fromColumnName();
+    method public abstract String tableName();
+    method public abstract String toColumnName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface RenameColumn.Entries {
+    method public abstract androidx.room.RenameColumn[] value();
+  }
+
+  @java.lang.annotation.Repeatable(RenameTable.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface RenameTable {
+    method public abstract String fromTableName();
+    method public abstract String toTableName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface RenameTable.Entries {
+    method public abstract androidx.room.RenameTable[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface RewriteQueriesToDropUnusedColumns {
+  }
+
+  public class RoomWarnings {
+    ctor @Deprecated public RoomWarnings();
+    field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+    field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+    field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+    field public static final String DOES_NOT_IMPLEMENT_EQUALS_HASHCODE = "ROOM_TYPE_DOES_NOT_IMPLEMENT_EQUALS_HASHCODE";
+    field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+    field public static final String MISMATCHED_GETTER = "ROOM_MISMATCHED_GETTER_TYPE";
+    field public static final String MISMATCHED_SETTER = "ROOM_MISMATCHED_SETTER_TYPE";
+    field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+    field public static final String MISSING_INDEX_ON_JUNCTION = "MISSING_INDEX_ON_JUNCTION";
+    field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+    field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+    field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+    field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+    field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface SkipQueryVerification {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Transaction {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface TypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface TypeConverters {
+    method public abstract androidx.room.BuiltInTypeConverters builtInTypeConverters() default @androidx.room.BuiltInTypeConverters;
+    method public abstract Class<?>[] value() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+}
+
diff --git a/room/room-common/api/public_plus_experimental_2.4.0-beta02.txt b/room/room-common/api/public_plus_experimental_2.4.0-beta02.txt
new file mode 100644
index 0000000..74796f1
--- /dev/null
+++ b/room/room-common/api/public_plus_experimental_2.4.0-beta02.txt
@@ -0,0 +1,276 @@
+// Signature format: 4.0
+package androidx.room {
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface AutoMigration {
+    method public abstract int from();
+    method public abstract Class<?> spec() default java.lang.Object.class;
+    method public abstract int to();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface BuiltInTypeConverters {
+    method public abstract androidx.room.BuiltInTypeConverters.State enums() default androidx.room.BuiltInTypeConverters.State.INHERITED;
+    method public abstract androidx.room.BuiltInTypeConverters.State uuid() default androidx.room.BuiltInTypeConverters.State.INHERITED;
+  }
+
+  public enum BuiltInTypeConverters.State {
+    enum_constant public static final androidx.room.BuiltInTypeConverters.State DISABLED;
+    enum_constant public static final androidx.room.BuiltInTypeConverters.State ENABLED;
+    enum_constant public static final androidx.room.BuiltInTypeConverters.State INHERITED;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ColumnInfo {
+    method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+    method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+    method public abstract boolean index() default false;
+    method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+    method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+    field public static final int BINARY = 2; // 0x2
+    field public static final int BLOB = 5; // 0x5
+    field public static final String INHERIT_FIELD_NAME = "[field-name]";
+    field public static final int INTEGER = 3; // 0x3
+    field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+    field public static final int NOCASE = 3; // 0x3
+    field public static final int REAL = 4; // 0x4
+    field public static final int RTRIM = 4; // 0x4
+    field public static final int TEXT = 2; // 0x2
+    field public static final int UNDEFINED = 1; // 0x1
+    field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+    field public static final int UNSPECIFIED = 1; // 0x1
+    field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Dao {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Database {
+    method public abstract androidx.room.AutoMigration[] autoMigrations() default {};
+    method public abstract Class<?>[] entities();
+    method public abstract boolean exportSchema() default true;
+    method public abstract int version();
+    method public abstract Class<?>[] views() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DatabaseView {
+    method public abstract String value() default "";
+    method public abstract String viewName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Delete {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+  }
+
+  @java.lang.annotation.Repeatable(DeleteColumn.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DeleteColumn {
+    method public abstract String columnName();
+    method public abstract String tableName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface DeleteColumn.Entries {
+    method public abstract androidx.room.DeleteColumn[] value();
+  }
+
+  @java.lang.annotation.Repeatable(DeleteTable.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DeleteTable {
+    method public abstract String tableName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface DeleteTable.Entries {
+    method public abstract androidx.room.DeleteTable[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Embedded {
+    method public abstract String prefix() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Entity {
+    method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+    method public abstract String[] ignoredColumns() default {};
+    method public abstract androidx.room.Index[] indices() default {};
+    method public abstract boolean inheritSuperIndices() default false;
+    method public abstract String[] primaryKeys() default {};
+    method public abstract String tableName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface ForeignKey {
+    method public abstract String[] childColumns();
+    method public abstract boolean deferred() default false;
+    method public abstract Class<?> entity();
+    method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+    method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+    method public abstract String[] parentColumns();
+    field public static final int CASCADE = 5; // 0x5
+    field public static final int NO_ACTION = 1; // 0x1
+    field public static final int RESTRICT = 2; // 0x2
+    field public static final int SET_DEFAULT = 4; // 0x4
+    field public static final int SET_NULL = 3; // 0x3
+  }
+
+  @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+    method public abstract Class<?> contentEntity() default java.lang.Object.class;
+    method public abstract String languageId() default "";
+    method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+    method public abstract String[] notIndexed() default {};
+    method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+    method public abstract int[] prefix() default {};
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  public class FtsOptions {
+    field public static final String TOKENIZER_ICU = "icu";
+    field public static final String TOKENIZER_PORTER = "porter";
+    field public static final String TOKENIZER_SIMPLE = "simple";
+    field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+  }
+
+  public enum FtsOptions.MatchInfo {
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+  }
+
+  public enum FtsOptions.Order {
+    enum_constant public static final androidx.room.FtsOptions.Order ASC;
+    enum_constant public static final androidx.room.FtsOptions.Order DESC;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface Ignore {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
+    method public abstract String name() default "";
+    method public abstract androidx.room.Index.Order[] orders() default {};
+    method public abstract boolean unique() default false;
+    method public abstract String[] value();
+  }
+
+  public enum Index.Order {
+    enum_constant public static final androidx.room.Index.Order ASC;
+    enum_constant public static final androidx.room.Index.Order DESC;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Junction {
+    method public abstract String entityColumn() default "";
+    method public abstract String parentColumn() default "";
+    method public abstract Class<?> value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface MapInfo {
+    method public abstract String keyColumn() default "";
+    method public abstract String valueColumn() default "";
+  }
+
+  @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface OnConflictStrategy {
+    field public static final int ABORT = 3; // 0x3
+    field @Deprecated public static final int FAIL = 4; // 0x4
+    field public static final int IGNORE = 5; // 0x5
+    field public static final int REPLACE = 1; // 0x1
+    field @Deprecated public static final int ROLLBACK = 2; // 0x2
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface PrimaryKey {
+    method public abstract boolean autoGenerate() default false;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedAutoMigrationSpec {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Query {
+    method public abstract String value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface RawQuery {
+    method public abstract Class<?>[] observedEntities() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Relation {
+    method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method public abstract String entityColumn();
+    method public abstract String parentColumn();
+    method public abstract String[] projection() default {};
+  }
+
+  @java.lang.annotation.Repeatable(RenameColumn.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface RenameColumn {
+    method public abstract String fromColumnName();
+    method public abstract String tableName();
+    method public abstract String toColumnName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface RenameColumn.Entries {
+    method public abstract androidx.room.RenameColumn[] value();
+  }
+
+  @java.lang.annotation.Repeatable(RenameTable.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface RenameTable {
+    method public abstract String fromTableName();
+    method public abstract String toTableName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface RenameTable.Entries {
+    method public abstract androidx.room.RenameTable[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface RewriteQueriesToDropUnusedColumns {
+  }
+
+  public class RoomWarnings {
+    ctor @Deprecated public RoomWarnings();
+    field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+    field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+    field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+    field public static final String DOES_NOT_IMPLEMENT_EQUALS_HASHCODE = "ROOM_TYPE_DOES_NOT_IMPLEMENT_EQUALS_HASHCODE";
+    field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+    field public static final String MISMATCHED_GETTER = "ROOM_MISMATCHED_GETTER_TYPE";
+    field public static final String MISMATCHED_SETTER = "ROOM_MISMATCHED_SETTER_TYPE";
+    field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+    field public static final String MISSING_INDEX_ON_JUNCTION = "MISSING_INDEX_ON_JUNCTION";
+    field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+    field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+    field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+    field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+    field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface SkipQueryVerification {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Transaction {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface TypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface TypeConverters {
+    method public abstract androidx.room.BuiltInTypeConverters builtInTypeConverters() default @androidx.room.BuiltInTypeConverters;
+    method public abstract Class<?>[] value() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+}
+
diff --git a/room/room-common/api/restricted_2.4.0-beta02.txt b/room/room-common/api/restricted_2.4.0-beta02.txt
new file mode 100644
index 0000000..51036df
--- /dev/null
+++ b/room/room-common/api/restricted_2.4.0-beta02.txt
@@ -0,0 +1,285 @@
+// Signature format: 4.0
+package androidx.room {
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface AutoMigration {
+    method public abstract int from();
+    method public abstract Class<?> spec() default java.lang.Object.class;
+    method public abstract int to();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface BuiltInTypeConverters {
+    method public abstract androidx.room.BuiltInTypeConverters.State enums() default androidx.room.BuiltInTypeConverters.State.INHERITED;
+    method public abstract androidx.room.BuiltInTypeConverters.State uuid() default androidx.room.BuiltInTypeConverters.State.INHERITED;
+  }
+
+  public enum BuiltInTypeConverters.State {
+    enum_constant public static final androidx.room.BuiltInTypeConverters.State DISABLED;
+    enum_constant public static final androidx.room.BuiltInTypeConverters.State ENABLED;
+    enum_constant public static final androidx.room.BuiltInTypeConverters.State INHERITED;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface ColumnInfo {
+    method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+    method public abstract String defaultValue() default androidx.room.ColumnInfo.VALUE_UNSPECIFIED;
+    method public abstract boolean index() default false;
+    method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+    method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+    field public static final int BINARY = 2; // 0x2
+    field public static final int BLOB = 5; // 0x5
+    field public static final String INHERIT_FIELD_NAME = "[field-name]";
+    field public static final int INTEGER = 3; // 0x3
+    field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+    field public static final int NOCASE = 3; // 0x3
+    field public static final int REAL = 4; // 0x4
+    field public static final int RTRIM = 4; // 0x4
+    field public static final int TEXT = 2; // 0x2
+    field public static final int UNDEFINED = 1; // 0x1
+    field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+    field public static final int UNSPECIFIED = 1; // 0x1
+    field public static final String VALUE_UNSPECIFIED = "[value-unspecified]";
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+  }
+
+  @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Dao {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Database {
+    method public abstract androidx.room.AutoMigration[] autoMigrations() default {};
+    method public abstract Class<?>[] entities();
+    method public abstract boolean exportSchema() default true;
+    method public abstract int version();
+    method public abstract Class<?>[] views() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DatabaseView {
+    method public abstract String value() default "";
+    method public abstract String viewName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Delete {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+  }
+
+  @java.lang.annotation.Repeatable(DeleteColumn.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DeleteColumn {
+    method public abstract String columnName();
+    method public abstract String tableName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface DeleteColumn.Entries {
+    method public abstract androidx.room.DeleteColumn[] value();
+  }
+
+  @java.lang.annotation.Repeatable(DeleteTable.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface DeleteTable {
+    method public abstract String tableName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface DeleteTable.Entries {
+    method public abstract androidx.room.DeleteTable[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Embedded {
+    method public abstract String prefix() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Entity {
+    method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+    method public abstract String[] ignoredColumns() default {};
+    method public abstract androidx.room.Index[] indices() default {};
+    method public abstract boolean inheritSuperIndices() default false;
+    method public abstract String[] primaryKeys() default {};
+    method public abstract String tableName() default "";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface ForeignKey {
+    method public abstract String[] childColumns();
+    method public abstract boolean deferred() default false;
+    method public abstract Class<?> entity();
+    method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+    method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+    method public abstract String[] parentColumns();
+    field public static final int CASCADE = 5; // 0x5
+    field public static final int NO_ACTION = 1; // 0x1
+    field public static final int RESTRICT = 2; // 0x2
+    field public static final int SET_DEFAULT = 4; // 0x4
+    field public static final int SET_NULL = 3; // 0x3
+  }
+
+  @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ForeignKey.Action {
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+    method public abstract Class<?> contentEntity() default java.lang.Object.class;
+    method public abstract String languageId() default "";
+    method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+    method public abstract String[] notIndexed() default {};
+    method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+    method public abstract int[] prefix() default {};
+    method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+    method public abstract String[] tokenizerArgs() default {};
+  }
+
+  public class FtsOptions {
+    field public static final String TOKENIZER_ICU = "icu";
+    field public static final String TOKENIZER_PORTER = "porter";
+    field public static final String TOKENIZER_SIMPLE = "simple";
+    field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+  }
+
+  public enum FtsOptions.MatchInfo {
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+    enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+  }
+
+  public enum FtsOptions.Order {
+    enum_constant public static final androidx.room.FtsOptions.Order ASC;
+    enum_constant public static final androidx.room.FtsOptions.Order DESC;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface Ignore {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Index {
+    method public abstract String name() default "";
+    method public abstract androidx.room.Index.Order[] orders() default {};
+    method public abstract boolean unique() default false;
+    method public abstract String[] value();
+  }
+
+  public enum Index.Order {
+    enum_constant public static final androidx.room.Index.Order ASC;
+    enum_constant public static final androidx.room.Index.Order DESC;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Insert {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({}) public @interface Junction {
+    method public abstract String entityColumn() default "";
+    method public abstract String parentColumn() default "";
+    method public abstract Class<?> value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface MapInfo {
+    method public abstract String keyColumn() default "";
+    method public abstract String valueColumn() default "";
+  }
+
+  @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface OnConflictStrategy {
+    field public static final int ABORT = 3; // 0x3
+    field @Deprecated public static final int FAIL = 4; // 0x4
+    field public static final int IGNORE = 5; // 0x5
+    field public static final int REPLACE = 1; // 0x1
+    field @Deprecated public static final int ROLLBACK = 2; // 0x2
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface PrimaryKey {
+    method public abstract boolean autoGenerate() default false;
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedAutoMigrationSpec {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface Query {
+    method public abstract String value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface RawQuery {
+    method public abstract Class<?>[] observedEntities() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public @interface Relation {
+    method public abstract androidx.room.Junction associateBy() default @androidx.room.Junction;
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method public abstract String entityColumn();
+    method public abstract String parentColumn();
+    method public abstract String[] projection() default {};
+  }
+
+  @java.lang.annotation.Repeatable(RenameColumn.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface RenameColumn {
+    method public abstract String fromColumnName();
+    method public abstract String tableName();
+    method public abstract String toColumnName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface RenameColumn.Entries {
+    method public abstract androidx.room.RenameColumn[] value();
+  }
+
+  @java.lang.annotation.Repeatable(RenameTable.Entries.class) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface RenameTable {
+    method public abstract String fromTableName();
+    method public abstract String toTableName();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public static @interface RenameTable.Entries {
+    method public abstract androidx.room.RenameTable[] value();
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface RewriteQueriesToDropUnusedColumns {
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomMasterTable {
+    method public static String! createInsertQuery(String!);
+    field public static final String CREATE_QUERY = "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)";
+    field public static final String DEFAULT_ID = "42";
+    field public static final String NAME = "room_master_table";
+    field public static final String READ_QUERY = "SELECT identity_hash FROM room_master_table WHERE id = 42 LIMIT 1";
+    field public static final String TABLE_NAME = "room_master_table";
+  }
+
+  public class RoomWarnings {
+    ctor @Deprecated public RoomWarnings();
+    field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+    field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+    field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+    field public static final String DOES_NOT_IMPLEMENT_EQUALS_HASHCODE = "ROOM_TYPE_DOES_NOT_IMPLEMENT_EQUALS_HASHCODE";
+    field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+    field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+    field public static final String MISMATCHED_GETTER = "ROOM_MISMATCHED_GETTER_TYPE";
+    field public static final String MISMATCHED_SETTER = "ROOM_MISMATCHED_SETTER_TYPE";
+    field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+    field public static final String MISSING_INDEX_ON_JUNCTION = "MISSING_INDEX_ON_JUNCTION";
+    field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+    field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+    field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+    field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+    field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) public @interface SkipQueryVerification {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface Transaction {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface TypeConverter {
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface TypeConverters {
+    method public abstract androidx.room.BuiltInTypeConverters builtInTypeConverters() default @androidx.room.BuiltInTypeConverters;
+    method public abstract Class<?>[] value() default {};
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+    method public abstract Class<?> entity() default java.lang.Object.class;
+    method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+  }
+
+}
+
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/RoomKspProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/RoomKspProcessor.kt
index 7437810..0733517 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/RoomKspProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/RoomKspProcessor.kt
@@ -17,9 +17,12 @@
 package androidx.room
 
 import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor
+import androidx.room.processor.Context.BooleanProcessorOptions.USE_NULL_AWARE_CONVERTER
+import androidx.room.processor.ProcessorErrors
 import com.google.devtools.ksp.processing.SymbolProcessor
 import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 import com.google.devtools.ksp.processing.SymbolProcessorProvider
+import javax.tools.Diagnostic
 
 /**
  * Entry point for processing using KSP.
@@ -27,7 +30,22 @@
 class RoomKspProcessor(
     environment: SymbolProcessorEnvironment
 ) : KspBasicAnnotationProcessor(environment) {
-
+    init {
+        // print a warning if null aware converter is disabled because we'll remove that ability
+        // soon.
+        if (USE_NULL_AWARE_CONVERTER.getInputValue(xProcessingEnv) == false) {
+            xProcessingEnv.messager.printMessage(
+                kind = Diagnostic.Kind.WARNING,
+                msg = """
+                    Disabling null-aware type analysis in KSP is a temporary flag that will be
+                    removed in a future release.
+                    If the null-aware type analysis is causing a bug in your application,
+                    please file a bug at ${ProcessorErrors.ISSUE_TRACKER_LINK} with
+                    a sample app that reproduces your problem.
+                """.trimIndent()
+            )
+        }
+    }
     override fun processingSteps() = listOf(
         DatabaseProcessingStep()
     )
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
index 69d32c6..b024579 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
@@ -129,6 +129,7 @@
 object CollectionTypeNames {
     val ARRAY_MAP: ClassName = ClassName.get(COLLECTION_PACKAGE, "ArrayMap")
     val LONG_SPARSE_ARRAY: ClassName = ClassName.get(COLLECTION_PACKAGE, "LongSparseArray")
+    val INT_SPARSE_ARRAY: ClassName = ClassName.get(COLLECTION_PACKAGE, "SparseArrayCompat")
 }
 
 object CommonTypeNames {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
index 537ceff..791a90e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
@@ -41,6 +41,15 @@
     val checker: Checks = Checks(logger)
     val COMMON_TYPES = CommonTypes(processingEnv)
 
+    /**
+     * Checks whether we should use the TypeConverter store that has a specific heuristic for
+     * nullability. Defaults to true in KSP, false in javac.
+     */
+    val useNullAwareConverter: Boolean by lazy {
+        BooleanProcessorOptions.USE_NULL_AWARE_CONVERTER.getInputValue(processingEnv)
+            ?: (processingEnv.backend == XProcessingEnv.Backend.KSP)
+    }
+
     val typeAdapterStore by lazy {
         if (inheritedAdapterStore != null) {
             TypeAdapterStore.copy(this, inheritedAdapterStore)
@@ -207,19 +216,21 @@
 
     enum class BooleanProcessorOptions(val argName: String, private val defaultValue: Boolean) {
         INCREMENTAL("room.incremental", defaultValue = true),
-        EXPAND_PROJECTION("room.expandProjection", defaultValue = false);
+        EXPAND_PROJECTION("room.expandProjection", defaultValue = false),
+        USE_NULL_AWARE_CONVERTER("room.useNullAwareTypeAnalysis", defaultValue = false);
 
         /**
          * Returns the value of this option passed through the [XProcessingEnv]. If the value
          * is null or blank, it returns the default value instead.
          */
         fun getValue(processingEnv: XProcessingEnv): Boolean {
-            val value = processingEnv.options[argName]
-            return if (value.isNullOrBlank()) {
-                defaultValue
-            } else {
-                value.toBoolean()
-            }
+            return getInputValue(processingEnv) ?: defaultValue
+        }
+
+        fun getInputValue(processingEnv: XProcessingEnv): Boolean? {
+            return processingEnv.options[argName]?.takeIf {
+                it.isNotBlank()
+            }?.toBoolean()
         }
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/NullAwareTypeConverterStore.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/NullAwareTypeConverterStore.kt
new file mode 100644
index 0000000..95e4a8f
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/NullAwareTypeConverterStore.kt
@@ -0,0 +1,429 @@
+/*
+ * 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.room.solver
+
+import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.processing.XNullability.NONNULL
+import androidx.room.compiler.processing.XNullability.NULLABLE
+import androidx.room.compiler.processing.XNullability.UNKNOWN
+import androidx.room.compiler.processing.XProcessingEnv.Backend
+import androidx.room.compiler.processing.XType
+import androidx.room.processor.Context
+import androidx.room.solver.types.CompositeTypeConverter
+import androidx.room.solver.types.NoOpConverter
+import androidx.room.solver.types.NullSafeTypeConverter
+import androidx.room.solver.types.RequireNotNullTypeConverter
+import androidx.room.solver.types.TypeConverter
+import androidx.room.solver.types.UpCastTypeConverter
+import java.util.PriorityQueue
+
+/**
+ * A [TypeConverterStore] implementation that generates better code when we have the nullability
+ * information in types. It is enabled by default only in KSP backend but it can also be turned
+ * on via the [Context.BooleanProcessorOptions.USE_NULL_AWARE_CONVERTER] flag.
+ *
+ * This [TypeConverterStore] tries to maintain the nullability of the input/output type
+ * when writing into/reading from database. Even though nullability preservation is preferred, it is
+ * not strictly required such that it will fall back to the mismatched nullability.
+ */
+class NullAwareTypeConverterStore(
+    context: Context,
+    /**
+     * Available TypeConverters. Note that we might synthesize new type converters based on this
+     * list.
+     */
+    typeConverters: List<TypeConverter>,
+    /**
+     * List of types that can be saved into db/read from without a converter.
+     */
+    private val knownColumnTypes: List<XType>
+) : TypeConverterStore {
+    override val typeConverters = if (context.processingEnv.backend == Backend.KSP) {
+        val processedConverters = typeConverters.toMutableList()
+        // create copies for converters that receive non-null values
+        typeConverters.forEach { converter ->
+            if (converter.from.nullability == NONNULL) {
+                val candidate = NullSafeTypeConverter(delegate = converter)
+                // before we add this null safe converter, make sure there is no other converter
+                // that would already handle the same arguments.
+                val match = processedConverters.any { other ->
+                    other.from.isAssignableFrom(candidate.from) &&
+                        candidate.to.isAssignableFrom(other.to)
+                }
+                if (!match) {
+                    processedConverters.add(candidate)
+                }
+            }
+        }
+        processedConverters
+    } else {
+        typeConverters
+    }
+
+    // cache for type converter lookups to avoid traversing all of the list every time we need to
+    // find possible converters for a type. Unlike JAVAC, KSP supports equality in its objects so
+    // this tends to work rather well.
+    private val typeConvertersByFromCache = mutableMapOf<XType, List<TypeConverter>>()
+    private val typeConvertersByToCache = mutableMapOf<XType, List<TypeConverter>>()
+
+    /**
+     * Known column types that are nullable.
+     * Used in [getColumnTypesInPreferenceBuckets] to avoid re-partitioning known type lists.
+     */
+    private val knownNullableColumnTypes by lazy {
+        knownColumnTypes.filter { it.nullability == NULLABLE }
+    }
+
+    /**
+     * Known column types that are non-null or have unknown nullability.
+     * Used in [getColumnTypesInPreferenceBuckets] to avoid re-partitioning known type lists.
+     */
+    private val knownNonNullableColumnTypes by lazy {
+        knownColumnTypes.filter { it.nullability != NULLABLE }
+    }
+
+    /**
+     * Returns a list of lists for the given type, ordered by preference buckets for
+     * the given nullability.
+     */
+    private fun getColumnTypesInPreferenceBuckets(
+        nullability: XNullability,
+        explicitColumnTypes: List<XType>?
+    ): List<List<XType>> {
+        return if (explicitColumnTypes == null) {
+            when (nullability) {
+                NULLABLE -> {
+                    // prioritize nulls
+                    listOf(
+                        knownNullableColumnTypes,
+                        knownNonNullableColumnTypes
+                    )
+                }
+                NONNULL -> {
+                    // prioritize non-null
+                    listOf(
+                        knownNonNullableColumnTypes,
+                        knownNullableColumnTypes
+                    )
+                }
+                else -> {
+                    // we don't know, YOLO
+                    listOf(knownColumnTypes)
+                }
+            }
+        } else {
+            when (nullability) {
+                UNKNOWN -> listOf(explicitColumnTypes)
+                else -> listOf(
+                    explicitColumnTypes.filter { it.nullability == nullability },
+                    explicitColumnTypes.filter { it.nullability != nullability }
+                )
+            }
+        }
+    }
+
+    override fun findConverterIntoStatement(
+        input: XType,
+        columnTypes: List<XType>?
+    ): TypeConverter? {
+        getColumnTypesInPreferenceBuckets(
+            nullability = input.nullability,
+            explicitColumnTypes = columnTypes
+        ).forEach { types ->
+            findConverterIntoStatementInternal(
+                input = input,
+                columnTypes = types
+            )?.getOrCreateConverter()?.let {
+                return it
+            }
+        }
+        return null
+    }
+
+    private fun findConverterIntoStatementInternal(
+        input: XType,
+        columnTypes: List<XType>
+    ): TypeConverterEntry? {
+        if (columnTypes.isEmpty()) return null
+        val queue = TypeConverterQueue(
+            sourceType = input,
+            // each converter is keyed on which type they will take us to
+            keyType = TypeConverter::to
+        )
+
+        while (true) {
+            val current = queue.next() ?: break
+            val match = columnTypes.any { columnType ->
+                columnType.isSameType(current.type)
+            }
+            if (match) {
+                return current
+            }
+            // check for assignable matches but only enqueue them as there might be another shorter
+            // path
+            columnTypes.forEach { columnType ->
+                if (columnType.isAssignableFrom(current.type)) {
+                    queue.maybeEnqueue(
+                        current.appendConverter(
+                            UpCastTypeConverter(
+                                upCastFrom = current.type,
+                                upCastTo = columnType
+                            )
+                        )
+                    )
+                }
+            }
+            getAllTypeConvertersFrom(current.type).forEach {
+                queue.maybeEnqueue(current.appendConverter(it))
+            }
+        }
+        return null
+    }
+
+    override fun findConverterFromCursor(
+        columnTypes: List<XType>?,
+        output: XType
+    ): TypeConverter? {
+        @Suppress("NAME_SHADOWING") // intentional
+        val columnTypes = columnTypes ?: knownColumnTypes
+        // prefer nullable when reading from database, regardless of the output type
+        getColumnTypesInPreferenceBuckets(
+            nullability = NULLABLE,
+            explicitColumnTypes = columnTypes
+        ).forEach { types ->
+            findConverterFromCursorInternal(
+                columnTypes = types,
+                output = output
+            )?.let {
+                return it.getOrCreateConverter()
+            }
+        }
+
+        // if type is non-null, try to find nullable and add null check
+        return if (output.nullability == NONNULL) {
+            findConverterFromCursorInternal(
+                columnTypes = columnTypes,
+                output = output.makeNullable()
+            )?.appendConverter(
+                RequireNotNullTypeConverter(
+                    from = output.makeNullable()
+                )
+            )
+        } else {
+            null
+        }
+    }
+
+    private fun findConverterFromCursorInternal(
+        columnTypes: List<XType>,
+        output: XType
+    ): TypeConverterEntry? {
+        if (columnTypes.isEmpty()) return null
+        val queue = TypeConverterQueue(
+            sourceType = output,
+            // each converter is keyed on which type they receive as we are doing pathfinding
+            // reverse here
+            keyType = TypeConverter::from
+        )
+
+        while (true) {
+            val current = queue.next() ?: break
+            val match = columnTypes.any { columnType ->
+                columnType.isSameType(current.type)
+            }
+            if (match) {
+                return current
+            }
+            // check for assignable matches but only enqueue them as there might be another shorter
+            // path
+            columnTypes.forEach { columnType ->
+                if (current.type.isAssignableFrom(columnType)) {
+                    queue.maybeEnqueue(
+                        current.prependConverter(
+                            UpCastTypeConverter(
+                                upCastFrom = columnType,
+                                upCastTo = current.type
+                            )
+                        )
+                    )
+                }
+            }
+            getAllTypeConvertersTo(current.type).forEach {
+                queue.maybeEnqueue(current.prependConverter(it))
+            }
+        }
+        return null
+    }
+
+    override fun findTypeConverter(input: XType, output: XType): TypeConverter? {
+        return findConverterIntoStatementInternal(
+            input = input,
+            columnTypes = listOf(output)
+        )?.getOrCreateConverter()
+    }
+
+    /**
+     * Returns all type converters that can receive input type and return into another type.
+     */
+    private fun getAllTypeConvertersFrom(
+        input: XType
+    ): List<TypeConverter> {
+        // for input, check assignability because it defines whether we can use the method or not.
+        return typeConvertersByFromCache.getOrPut(input) {
+            // this cache avoids us many assignability checks.
+            typeConverters.mapNotNull { converter ->
+                when {
+                    converter.from.isSameType(input) -> converter
+                    converter.from.isAssignableFrom(input) -> CompositeTypeConverter(
+                        conv1 = UpCastTypeConverter(
+                            upCastFrom = input,
+                            upCastTo = converter.from
+                        ),
+                        conv2 = converter
+                    )
+                    else -> null
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns all type converters that can return the output type.
+     */
+    private fun getAllTypeConvertersTo(
+        output: XType
+    ): List<TypeConverter> {
+        return typeConvertersByToCache.getOrPut(output) {
+            // this cache avoids us many assignability checks.
+            typeConverters.mapNotNull { converter ->
+                when {
+                    converter.to.isSameType(output) -> converter
+                    output.isAssignableFrom(converter.to) -> CompositeTypeConverter(
+                        conv1 = converter,
+                        conv2 = UpCastTypeConverter(
+                            upCastFrom = converter.to,
+                            upCastTo = output
+                        )
+                    )
+                    else -> null
+                }
+            }
+        }
+    }
+
+    /**
+     * Priority queue for the type converter search.
+     */
+    private class TypeConverterQueue(
+        sourceType: XType,
+        val keyType: TypeConverter.() -> XType
+    ) {
+        // using insertion order as the tie breaker for reproducible builds.
+        private var insertionOrder = 0
+
+        // map of XType to the converter that includes the path from the source type to the XType.
+        private val cheapestEntry = mutableMapOf<XType, TypeConverterEntry>()
+        private val queue = PriorityQueue<TypeConverterEntry>()
+
+        init {
+            val typeConverterEntry = TypeConverterEntry(
+                tieBreakerPriority = insertionOrder++,
+                type = sourceType,
+                converter = null
+            )
+            cheapestEntry[sourceType] = typeConverterEntry
+            queue.add(typeConverterEntry)
+        }
+
+        fun next(): TypeConverterEntry? {
+            while (queue.isNotEmpty()) {
+                val entry = queue.remove()
+                // check if we processed this type as there is no reason to process it again
+                if (cheapestEntry[entry.type] !== entry) {
+                    continue
+                }
+                return entry
+            }
+            return null
+        }
+
+        /**
+         * Enqueues the given [converter] if its target type (defined by [keyType]) is not visited
+         * or visited with a more expensive converter.
+         */
+        fun maybeEnqueue(
+            converter: TypeConverter
+        ): Boolean {
+            val keyType = converter.keyType()
+            val existing = cheapestEntry[keyType]
+            if (existing == null ||
+                (existing.converter != null && existing.converter.cost > converter.cost)
+            ) {
+                val entry = TypeConverterEntry(insertionOrder++, keyType, converter)
+                cheapestEntry[keyType] = entry
+                queue.add(entry)
+                return true
+            }
+            return false
+        }
+    }
+
+    private data class TypeConverterEntry(
+        // when costs are equal, tieBreakerPriority is used
+        val tieBreakerPriority: Int,
+        val type: XType,
+        val converter: TypeConverter?
+    ) : Comparable<TypeConverterEntry> {
+        override fun compareTo(other: TypeConverterEntry): Int {
+            if (converter == null) {
+                if (other.converter != null) {
+                    return -1
+                }
+            } else if (other.converter == null) {
+                return 1
+            } else {
+                val costCmp = converter.cost.compareTo(other.converter.cost)
+                if (costCmp != 0) {
+                    return costCmp
+                }
+            }
+            return tieBreakerPriority.compareTo(other.tieBreakerPriority)
+        }
+
+        fun getOrCreateConverter() = converter ?: NoOpConverter(type)
+
+        fun appendConverter(nextConverter: TypeConverter): TypeConverter {
+            if (converter == null) {
+                return nextConverter
+            }
+            return CompositeTypeConverter(
+                conv1 = converter,
+                conv2 = nextConverter
+            )
+        }
+
+        fun prependConverter(previous: TypeConverter): TypeConverter {
+            if (converter == null) {
+                return previous
+            }
+            return CompositeTypeConverter(
+                conv1 = previous,
+                conv2 = converter
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index 937ec76..d8d51bf 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -20,6 +20,8 @@
 import androidx.room.compiler.processing.isArray
 import androidx.room.compiler.processing.isEnum
 import androidx.room.ext.CollectionTypeNames.ARRAY_MAP
+import androidx.room.ext.CollectionTypeNames.INT_SPARSE_ARRAY
+import androidx.room.ext.CollectionTypeNames.LONG_SPARSE_ARRAY
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.GuavaBaseTypeNames
 import androidx.room.ext.isEntityElement
@@ -110,6 +112,7 @@
 import com.google.common.collect.ImmutableMultimap
 import com.google.common.collect.ImmutableSetMultimap
 import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
 
 @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
 /**
@@ -172,11 +175,14 @@
             ByteArrayColumnTypeAdapter.create(context.processingEnv).forEach(::addColumnAdapter)
             ByteBufferColumnTypeAdapter.create(context.processingEnv).forEach(::addColumnAdapter)
             PrimitiveBooleanToIntConverter.create(context.processingEnv).forEach(::addTypeConverter)
+            // null aware converter is able to automatically null wrap converters so we don't
+            // need this as long as we are running in KSP
             BoxedBooleanToBoxedIntConverter.create(context.processingEnv)
                 .forEach(::addTypeConverter)
             return TypeAdapterStore(
                 context = context, columnTypeAdapters = adapters,
-                typeConverterStore = TypeConverterStore(
+                typeConverterStore = TypeConverterStore.create(
+                    context = context,
                     typeConverters = converters,
                     knownColumnTypes = adapters.map { it.out }
                 ),
@@ -553,10 +559,31 @@
                 immutableClassName = immutableClassName
             )
         } else if (typeMirror.isTypeOf(java.util.Map::class) ||
-            typeMirror.rawType.typeName == ARRAY_MAP
+            typeMirror.rawType.typeName == ARRAY_MAP ||
+            typeMirror.rawType.typeName == LONG_SPARSE_ARRAY ||
+            typeMirror.rawType.typeName == INT_SPARSE_ARRAY
         ) {
-            val keyTypeArg = typeMirror.typeArguments[0].extendsBoundOrSelf()
-            val mapValueTypeArg = typeMirror.typeArguments[1].extendsBoundOrSelf()
+            val keyTypeArg = if (typeMirror.rawType.typeName == LONG_SPARSE_ARRAY) {
+                context.processingEnv.requireType(TypeName.LONG)
+            } else if (typeMirror.rawType.typeName == INT_SPARSE_ARRAY) {
+                context.processingEnv.requireType(TypeName.INT)
+            } else {
+                typeMirror.typeArguments[0].extendsBoundOrSelf()
+            }
+
+            val isSparseArray = if (typeMirror.rawType.typeName == LONG_SPARSE_ARRAY) {
+                LONG_SPARSE_ARRAY
+            } else if (typeMirror.rawType.typeName == INT_SPARSE_ARRAY) {
+                INT_SPARSE_ARRAY
+            } else {
+                null
+            }
+
+            val mapValueTypeArg = if (isSparseArray != null) {
+                typeMirror.typeArguments[0].extendsBoundOrSelf()
+            } else {
+                typeMirror.typeArguments[1].extendsBoundOrSelf()
+            }
 
             if (mapValueTypeArg.typeElement == null) {
                 context.logger.e(
@@ -606,7 +633,8 @@
                         keyRowAdapter = keyRowAdapter,
                         valueRowAdapter = valueRowAdapter,
                         valueCollectionType = mapValueTypeArg,
-                        isArrayMap = typeMirror.rawType.typeName == ARRAY_MAP
+                        isArrayMap = typeMirror.rawType.typeName == ARRAY_MAP,
+                        isSparseArray = isSparseArray
                     )
                 } else {
                     context.logger.e(
@@ -639,7 +667,8 @@
                     keyRowAdapter = keyRowAdapter,
                     valueRowAdapter = valueRowAdapter,
                     valueCollectionType = null,
-                    isArrayMap = typeMirror.rawType.typeName == ARRAY_MAP
+                    isArrayMap = typeMirror.rawType.typeName == ARRAY_MAP,
+                    isSparseArray = isSparseArray
                 )
             }
         }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeConverterStore.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeConverterStore.kt
index fbd8863..0b7d0af 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeConverterStore.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeConverterStore.kt
@@ -17,49 +17,34 @@
 package androidx.room.solver
 
 import androidx.room.compiler.processing.XType
+import androidx.room.processor.Context
 import androidx.room.solver.types.CompositeTypeConverter
 import androidx.room.solver.types.NoOpConverter
+import androidx.room.solver.types.RequireNotNullTypeConverter
 import androidx.room.solver.types.TypeConverter
-import java.util.LinkedList
+import androidx.room.solver.types.UpCastTypeConverter
 
-/**
- * Common logic that handles conversion between types either using built in converters or user
- * provided type converters.
- */
-class TypeConverterStore(
-    /**
-     * Available TypeConverters
-     */
-    private val typeConverters: List<TypeConverter>,
-    /**
-     * List of types that can be saved into db/read from without a converter.
-     */
-    private val knownColumnTypes: List<XType>
-) {
+interface TypeConverterStore {
+    val typeConverters: List<TypeConverter>
+
     /**
      * Finds a [TypeConverter] (might be composite) that can convert the given [input] type into
      * one of the given [columnTypes]. If [columnTypes] is not specified, targets all
-     * [knownColumnTypes].
+     * `knownColumnTypes`.
      */
     fun findConverterIntoStatement(
         input: XType,
-        columnTypes: List<XType>? = null
-    ) = findTypeConverter(
-        inputs = listOf(input),
-        outputs = columnTypes ?: knownColumnTypes
-    )
+        columnTypes: List<XType>?
+    ): TypeConverter?
 
     /**
      * Finds a [TypeConverter] (might be composite) that can convert the given [columnTypes] into
-     * the [output] type. If [columnTypes] is not specified, uses all [knownColumnTypes].
+     * the [output] type. If [columnTypes] is not specified, uses all `knownColumnTypes`.
      */
     fun findConverterFromCursor(
-        columnTypes: List<XType>? = null,
+        columnTypes: List<XType>?,
         output: XType
-    ) = findTypeConverter(
-        inputs = columnTypes ?: knownColumnTypes,
-        outputs = listOf(output)
-    )
+    ): TypeConverter?
 
     /**
      * Finds a [TypeConverter] from [input] to [output].
@@ -67,104 +52,8 @@
     fun findTypeConverter(
         input: XType,
         output: XType
-    ) = findTypeConverter(
-        inputs = listOf(input),
-        outputs = listOf(output)
-    )
+    ): TypeConverter?
 
-    /**
-     * Finds a type converter that can convert one of the input values to one of the output values.
-     *
-     * When multiple conversion paths are possible, shortest path (least amount of conversion) is
-     * preferred.
-     */
-    private fun findTypeConverter(
-        inputs: List<XType>,
-        outputs: List<XType>
-    ): TypeConverter? {
-        if (inputs.isEmpty()) {
-            return null
-        }
-        inputs.forEach { input ->
-            if (outputs.any { output -> input.isSameType(output) }) {
-                return NoOpConverter(input)
-            }
-        }
-
-        val excludes = arrayListOf<XType>()
-
-        val queue = LinkedList<TypeConverter>()
-        fun List<TypeConverter>.findMatchingConverter(): TypeConverter? {
-            // We prioritize exact match over assignable. To do that, this variable keeps any
-            // assignable match and if we cannot find exactly same type match, we'll return the
-            // assignable match.
-            var assignableMatchFallback: TypeConverter? = null
-            this.forEach { converter ->
-                outputs.forEach { output ->
-                    if (output.isSameType(converter.to)) {
-                        return converter
-                    } else if (assignableMatchFallback == null &&
-                        output.isAssignableFrom(converter.to)
-                    ) {
-                        // if we don't find exact match, we'll return this.
-                        assignableMatchFallback = converter
-                    }
-                }
-            }
-            return assignableMatchFallback
-        }
-        inputs.forEach { input ->
-            val candidates = getAllTypeConverters(input, excludes)
-            val match = candidates.findMatchingConverter()
-            if (match != null) {
-                return match
-            }
-            candidates.forEach {
-                excludes.add(it.to)
-                queue.add(it)
-            }
-        }
-        excludes.addAll(inputs)
-        while (queue.isNotEmpty()) {
-            val prev = queue.pop()
-            val from = prev.to
-            val candidates = getAllTypeConverters(from, excludes)
-            val match = candidates.findMatchingConverter()
-            if (match != null) {
-                return CompositeTypeConverter(prev, match)
-            }
-            candidates.forEach {
-                excludes.add(it.to)
-                queue.add(CompositeTypeConverter(prev, it))
-            }
-        }
-        return null
-    }
-
-    /**
-     * Returns all type converters that can receive input type and return into another type.
-     * The returned list is ordered by priority such that if we have an exact match, it is
-     * prioritized.
-     */
-    private fun getAllTypeConverters(input: XType, excludes: List<XType>): List<TypeConverter> {
-        // for input, check assignability because it defines whether we can use the method or not.
-        // for excludes, use exact match
-        return typeConverters.filter { converter ->
-            converter.from.isAssignableFrom(input) &&
-                !excludes.any { it.isSameType(converter.to) }
-        }.sortedByDescending {
-            // if it is the same, prioritize
-            if (it.from.isSameType(input)) {
-                2
-            } else {
-                1
-            }
-        }
-    }
-
-    /**
-     * Tries to reverse the converter going through the same nodes, if possible.
-     */
     fun reverse(converter: TypeConverter): TypeConverter? {
         return when (converter) {
             is NoOpConverter -> converter
@@ -173,6 +62,11 @@
                 val r2 = reverse(converter.conv2) ?: return null
                 CompositeTypeConverter(r2, r1)
             }
+            // reverse of require not null is upcast since not null can be converted into nullable
+            is RequireNotNullTypeConverter -> UpCastTypeConverter(
+                upCastFrom = converter.to,
+                upCastTo = converter.from
+            )
             else -> {
                 typeConverters.firstOrNull {
                     it.from.isSameType(converter.to) &&
@@ -181,4 +75,30 @@
             }
         }
     }
+
+    companion object {
+        /**
+         * @param context Processing context
+         * @param typeConverters Available TypeConverters, ordered by priority when they have the
+         *        same cost.
+         * @param knownColumnTypes List of types that can be saved into db/read from without a
+         *        converter.
+         */
+        fun create(
+            context: Context,
+            typeConverters: List<TypeConverter>,
+            knownColumnTypes: List<XType>
+        ) = if (context.useNullAwareConverter) {
+            NullAwareTypeConverterStore(
+                context = context,
+                typeConverters = typeConverters,
+                knownColumnTypes = knownColumnTypes
+            )
+        } else {
+            TypeConverterStoreImpl(
+                typeConverters = typeConverters,
+                knownColumnTypes = knownColumnTypes
+            )
+        }
+    }
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeConverterStoreImpl.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeConverterStoreImpl.kt
new file mode 100644
index 0000000..848addf
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/TypeConverterStoreImpl.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.room.solver
+
+import androidx.room.compiler.processing.XType
+import androidx.room.solver.types.CompositeTypeConverter
+import androidx.room.solver.types.NoOpConverter
+import androidx.room.solver.types.TypeConverter
+import java.util.LinkedList
+
+/**
+ * Legacy [TypeConverterStore] implementation that does not assume we have type nullability
+ * information. It is kept around for backwards compatibility.
+ */
+class TypeConverterStoreImpl(
+    /**
+     * Available TypeConverters
+     */
+    override val typeConverters: List<TypeConverter>,
+    /**
+     * List of types that can be saved into db/read from without a converter.
+     */
+    private val knownColumnTypes: List<XType>
+) : TypeConverterStore {
+    override fun findConverterIntoStatement(
+        input: XType,
+        columnTypes: List<XType>?
+    ) = findTypeConverter(
+        inputs = listOf(input),
+        outputs = columnTypes ?: knownColumnTypes
+    )
+
+    override fun findConverterFromCursor(
+        columnTypes: List<XType>?,
+        output: XType
+    ) = findTypeConverter(
+        inputs = columnTypes ?: knownColumnTypes,
+        outputs = listOf(output)
+    )
+
+    override fun findTypeConverter(
+        input: XType,
+        output: XType
+    ) = findTypeConverter(
+        inputs = listOf(input),
+        outputs = listOf(output)
+    )
+
+    /**
+     * Finds a type converter that can convert one of the input values to one of the output values.
+     *
+     * When multiple conversion paths are possible, shortest path (least amount of conversion) is
+     * preferred.
+     */
+    private fun findTypeConverter(
+        inputs: List<XType>,
+        outputs: List<XType>
+    ): TypeConverter? {
+        if (inputs.isEmpty()) {
+            return null
+        }
+        inputs.forEach { input ->
+            if (outputs.any { output -> input.isSameType(output) }) {
+                return NoOpConverter(input)
+            }
+        }
+
+        val excludes = arrayListOf<XType>()
+
+        val queue = LinkedList<TypeConverter>()
+        fun List<TypeConverter>.findMatchingConverter(): TypeConverter? {
+            // We prioritize exact match over assignable. To do that, this variable keeps any
+            // assignable match and if we cannot find exactly same type match, we'll return the
+            // assignable match.
+            var assignableMatchFallback: TypeConverter? = null
+            this.forEach { converter ->
+                outputs.forEach { output ->
+                    if (output.isSameType(converter.to)) {
+                        return converter
+                    } else if (assignableMatchFallback == null &&
+                        output.isAssignableFrom(converter.to)
+                    ) {
+                        // if we don't find exact match, we'll return this.
+                        assignableMatchFallback = converter
+                    }
+                }
+            }
+            return assignableMatchFallback
+        }
+        inputs.forEach { input ->
+            val candidates = getAllTypeConverters(input, excludes)
+            val match = candidates.findMatchingConverter()
+            if (match != null) {
+                return match
+            }
+            candidates.forEach {
+                excludes.add(it.to)
+                queue.add(it)
+            }
+        }
+        excludes.addAll(inputs)
+        while (queue.isNotEmpty()) {
+            val prev = queue.pop()
+            val from = prev.to
+            val candidates = getAllTypeConverters(from, excludes)
+            val match = candidates.findMatchingConverter()
+            if (match != null) {
+                return CompositeTypeConverter(prev, match)
+            }
+            candidates.forEach {
+                excludes.add(it.to)
+                queue.add(CompositeTypeConverter(prev, it))
+            }
+        }
+        return null
+    }
+
+    /**
+     * Returns all type converters that can receive input type and return into another type.
+     * The returned list is ordered by priority such that if we have an exact match, it is
+     * prioritized.
+     */
+    private fun getAllTypeConverters(input: XType, excludes: List<XType>): List<TypeConverter> {
+        // for input, check assignability because it defines whether we can use the method or not.
+        // for excludes, use exact match
+        return typeConverters.filter { converter ->
+            converter.from.isAssignableFrom(input) &&
+                !excludes.any { it.isSameType(converter.to) }
+        }.sortedByDescending {
+            // if it is the same, prioritize
+            if (it.from.isSameType(input)) {
+                2
+            } else {
+                1
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MapQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MapQueryResultAdapter.kt
index d1f4465..b7add04 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MapQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MapQueryResultAdapter.kt
@@ -30,7 +30,8 @@
     private val keyRowAdapter: RowAdapter,
     private val valueRowAdapter: RowAdapter,
     private val valueCollectionType: XType?,
-    isArrayMap: Boolean = false
+    isArrayMap: Boolean = false,
+    private val isSparseArray: ClassName? = null
 ) : QueryResultAdapter(listOf(keyRowAdapter, valueRowAdapter)), MultimapQueryResultAdapter {
     private val declaredToConcreteCollection = mapOf<ClassName, ClassName>(
         ClassName.get(List::class.java) to ClassName.get(ArrayList::class.java),
@@ -55,26 +56,40 @@
         valueTypeArg.typeName
     }
 
-    private val mapType = ParameterizedTypeName.get(
-        if (isArrayMap) ARRAY_MAP else ClassName.get(Map::class.java),
-        keyTypeArg.typeName,
-        declaredValueType
-    )
+    private val mapType = if (isSparseArray != null) {
+        ParameterizedTypeName.get(
+            isSparseArray,
+            declaredValueType
+        )
+    } else {
+        ParameterizedTypeName.get(
+            if (isArrayMap) ARRAY_MAP else ClassName.get(Map::class.java),
+            keyTypeArg.typeName,
+            declaredValueType
+        )
+    }
 
     // LinkedHashMap is used as impl to preserve key ordering for ordered query results.
-    private val mapImplType = ParameterizedTypeName.get(
-        if (isArrayMap) ARRAY_MAP else ClassName.get(LinkedHashMap::class.java),
-        keyTypeArg.typeName,
-        declaredValueType
-    )
+    private val mapImplType =
+        if (isSparseArray != null) {
+            ParameterizedTypeName.get(
+                isSparseArray,
+                declaredValueType
+            )
+        } else {
+            ParameterizedTypeName.get(
+                if (isArrayMap) ARRAY_MAP else ClassName.get(LinkedHashMap::class.java),
+                keyTypeArg.typeName,
+                declaredValueType
+            )
+        }
 
     override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
         scope.builder().apply {
             keyRowAdapter.onCursorReady(cursorVarName, scope)
             valueRowAdapter.onCursorReady(cursorVarName, scope)
 
-            val mapVarName = outVarName
-            addStatement("final $T $L = new $T()", mapType, mapVarName, mapImplType)
+            addStatement("final $T $L = new $T()", mapType, outVarName, mapImplType)
 
             val tmpKeyVarName = scope.getTmpVar("_key")
             val tmpValueVarName = scope.getTmpVar("_value")
@@ -89,11 +104,16 @@
                     valueRowAdapter.convert(tmpValueVarName, cursorVarName, scope)
                     val tmpCollectionVarName = scope.getTmpVar("_values")
                     addStatement("$T $L", declaredValueType, tmpCollectionVarName)
-                    beginControlFlow("if ($L.containsKey($L))", mapVarName, tmpKeyVarName).apply {
+
+                    if (isSparseArray != null) {
+                        beginControlFlow("if ($L.get($L) != null)", outVarName, tmpKeyVarName)
+                    } else {
+                        beginControlFlow("if ($L.containsKey($L))", outVarName, tmpKeyVarName)
+                    }.apply {
                         addStatement(
                             "$L = $L.get($L)",
                             tmpCollectionVarName,
-                            mapVarName,
+                            outVarName,
                             tmpKeyVarName
                         )
                     }
@@ -101,7 +121,7 @@
                         addStatement("$L = new $T()", tmpCollectionVarName, concreteValueType)
                         addStatement(
                             "$L.put($L, $L)",
-                            mapVarName,
+                            outVarName,
                             tmpKeyVarName,
                             tmpCollectionVarName
                         )
@@ -119,8 +139,12 @@
                     // For consistency purposes, in the one-to-one object mapping case, if
                     // multiple values are encountered for the same key, we will only consider
                     // the first ever encountered mapping.
-                    beginControlFlow("if (!$L.containsKey($L))", mapVarName, tmpKeyVarName).apply {
-                        addStatement("$L.put($L, $L)", mapVarName, tmpKeyVarName, tmpValueVarName)
+                    if (isSparseArray != null) {
+                        beginControlFlow("if ($L.get($L) == null)", outVarName, tmpKeyVarName)
+                    } else {
+                        beginControlFlow("if (!$L.containsKey($L))", outVarName, tmpKeyVarName)
+                    }.apply {
+                        addStatement("$L.put($L, $L)", outVarName, tmpKeyVarName, tmpValueVarName)
                     }
                     endControlFlow()
                 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/CompositeTypeConverter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/CompositeTypeConverter.kt
index b9c7cb9..2bb2c0c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/CompositeTypeConverter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/CompositeTypeConverter.kt
@@ -22,7 +22,9 @@
  * combines 2 type converters
  */
 class CompositeTypeConverter(val conv1: TypeConverter, val conv2: TypeConverter) : TypeConverter(
-    conv1.from, conv2.to
+    from = conv1.from,
+    to = conv2.to,
+    cost = conv1.cost + conv2.cost
 ) {
     override fun doConvert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
         scope.builder().apply {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/NullAwareTypeConverters.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/NullAwareTypeConverters.kt
new file mode 100644
index 0000000..288899f
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/NullAwareTypeConverters.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.room.solver.types
+
+import androidx.annotation.VisibleForTesting
+import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.processing.XType
+import androidx.room.ext.L
+import androidx.room.ext.S
+import androidx.room.ext.T
+import androidx.room.solver.CodeGenScope
+import java.lang.IllegalStateException
+
+/**
+ * A type converter that checks if the input is null and returns null instead of calling the
+ * [delegate].
+ */
+class NullSafeTypeConverter(
+    @VisibleForTesting
+    internal val delegate: TypeConverter
+) : TypeConverter(
+    from = delegate.from.makeNullable(),
+    to = delegate.to.makeNullable(),
+    cost = delegate.cost + Cost.NULL_SAFE
+) {
+    init {
+        check(delegate.from.nullability == XNullability.NONNULL) {
+            "NullableWrapper can ony be used if the input type is non-nullable"
+        }
+    }
+
+    override fun doConvert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            beginControlFlow("if($L == null)", inputVarName)
+            addStatement("$L = null", outputVarName)
+            nextControlFlow("else")
+            delegate.convert(inputVarName, outputVarName, scope)
+            endControlFlow()
+        }
+    }
+}
+
+/**
+ * A [TypeConverter] that checks the value is `non-null` and throws if it is null.
+ */
+class RequireNotNullTypeConverter(
+    from: XType,
+) : TypeConverter(
+    from = from,
+    to = from.makeNonNullable(),
+    cost = Cost.REQUIRE_NOT_NULL
+) {
+    init {
+        check(from.nullability != XNullability.NONNULL) {
+            "No reason to null check a non-null input"
+        }
+    }
+
+    override fun doConvert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            beginControlFlow("if($L == null)", inputVarName)
+            addStatement(
+                "throw new $T($S)", IllegalStateException::class.java,
+                "Expected non-null ${from.typeName}, but it was null."
+            )
+            nextControlFlow("else").apply {
+                addStatement("$L = $L", outputVarName, inputVarName)
+            }
+            endControlFlow()
+        }
+    }
+
+    override fun doConvert(inputVarName: String, scope: CodeGenScope): String {
+        scope.builder().apply {
+            beginControlFlow("if($L == null)", inputVarName)
+            addStatement(
+                "throw new $T($S)", IllegalStateException::class.java,
+                "Expected non-null ${from.typeName}, but it was null."
+            )
+            endControlFlow()
+        }
+        return inputVarName
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/TypeConverter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/TypeConverter.kt
index fab9eb1..2f8ae17 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/TypeConverter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/TypeConverter.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.solver.types
 
+import androidx.annotation.VisibleForTesting
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.L
 import androidx.room.ext.T
@@ -24,7 +25,11 @@
 /**
  * A code generator that can convert from 1 type to another
  */
-abstract class TypeConverter(val from: XType, val to: XType) {
+abstract class TypeConverter(
+    val from: XType,
+    val to: XType,
+    val cost: Cost = Cost.CONVERTER
+) {
     /**
      * Should generate the code that will covert [inputVarName] of type [from] to [outputVarName]
      * of type [to]. This method *should not* declare the [outputVarName] as it is already
@@ -70,4 +75,123 @@
     ) {
         doConvert(inputVarName, outputVarName, scope)
     }
+
+    /**
+     * Represents the cost of a type converter.
+     *
+     * When calculating cost, we consider multiple types of conversions in ascending order, from
+     * cheapest to expensive:
+     * * `upcast`: Converts from a subtype to super type (e.g. Int to Number)
+     * * `nullSafeWrapper`: Adds a null check before calling the delegated converter or else
+     *    returns null.
+     * * `converter`: Unit converter
+     * * `requireNotNull`: Adds a null check before returning the delegated converter's value
+     *    or throws if the value is null.
+     *
+     * The comparison happens in buckets such that having 10 upcasts is still cheaper than having
+     * 1 nullSafeWrapper.
+     *
+     * Internally, this class uses an IntArray to keep its fields to optimize for readability in
+     * operators.
+     */
+    class Cost private constructor(
+        /**
+         * Values for each bucket, ordered from most expensive to least expensive.
+         */
+        private val values: IntArray
+    ) : Comparable<Cost> {
+        init {
+            require(values.size == Buckets.SIZE)
+        }
+
+        constructor(
+            converters: Int,
+            nullSafeWrapper: Int = 0,
+            upCasts: Int = 0,
+            requireNotNull: Int = 0
+        ) : this(
+            // NOTE: construction order here MUST match the [Buckets]
+            intArrayOf(
+                requireNotNull,
+                converters,
+                nullSafeWrapper,
+                upCasts
+            )
+        )
+
+        @VisibleForTesting
+        val upCasts: Int
+            get() = values[Buckets.UP_CAST]
+
+        @VisibleForTesting
+        val nullSafeWrapper: Int
+            get() = values[Buckets.NULL_SAFE]
+
+        @VisibleForTesting
+        val requireNotNull: Int
+            get() = values[Buckets.REQUIRE_NOT_NULL]
+
+        @VisibleForTesting
+        val converters: Int
+            get() = values[Buckets.CONVERTER]
+
+        operator fun plus(other: Cost) = Cost(
+            values = IntArray(Buckets.SIZE) { index ->
+                values[index] + other.values[index]
+            }
+        )
+
+        override operator fun compareTo(other: Cost): Int {
+            for (index in 0 until Buckets.SIZE) {
+                val cmp = values[index].compareTo(other.values[index])
+                if (cmp != 0) {
+                    return cmp
+                }
+            }
+            return 0
+        }
+
+        override fun toString() = buildString {
+            append("Cost[")
+            append("upcast:")
+            append(upCasts)
+            append(",nullsafe:")
+            append(nullSafeWrapper)
+            append(",converters:")
+            append(converters)
+            append(",requireNotNull:")
+            append(requireNotNull)
+            append("]")
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (other !is Cost) {
+                return false
+            }
+            return compareTo(other) == 0
+        }
+
+        override fun hashCode(): Int {
+            // we don't really use hash functions so this is good enough as a hash function.
+            return values[Buckets.CONVERTER]
+        }
+
+        companion object {
+            val UP_CAST = Cost(converters = 0, upCasts = 1)
+            val NULL_SAFE = Cost(converters = 0, nullSafeWrapper = 1)
+            val CONVERTER = Cost(converters = 1)
+            val REQUIRE_NOT_NULL = Cost(converters = 0, requireNotNull = 1)
+        }
+
+        /**
+         * Comparison buckets, ordered from the MOST expensive to LEAST expensive
+         */
+        private object Buckets {
+            const val REQUIRE_NOT_NULL = 0
+            const val CONVERTER = 1
+            const val NULL_SAFE = 2
+            const val UP_CAST = 3
+            const val SIZE = 4
+        }
+    }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/UpCastTypeConverter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/UpCastTypeConverter.kt
new file mode 100644
index 0000000..ad07028
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/UpCastTypeConverter.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.room.solver.types
+
+import androidx.room.compiler.processing.XType
+import androidx.room.ext.L
+import androidx.room.solver.CodeGenScope
+
+/**
+ * We have a special class for upcasting types in type converters. (e.g. Int to Number)
+ * It is used in the pathfinding to be more expensive than exactly matching calls to prioritize
+ * exact matches.
+ */
+class UpCastTypeConverter(
+    upCastFrom: XType,
+    upCastTo: XType
+) : TypeConverter(
+    from = upCastFrom,
+    to = upCastTo,
+    cost = Cost.UP_CAST
+) {
+    override fun doConvert(inputVarName: String, outputVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            addStatement("$L = $L", outputVarName, inputVarName)
+        }
+    }
+
+    override fun doConvert(inputVarName: String, scope: CodeGenScope): String {
+        // normally, we don't need to generate any code here but if the upcast is converting from
+        // a primitive to boxed; we need to. Otherwise, output value won't become an object and
+        // that might break the rest of the code generation (e.g. checking nullable on primitive)
+        return if (to.typeName.isBoxedPrimitive && from.typeName.isPrimitive) {
+            super.doConvert(inputVarName, scope)
+        } else {
+            inputVarName
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
new file mode 100644
index 0000000..99a0162
--- /dev/null
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/NullabilityAwareTypeConverterStoreTest.kt
@@ -0,0 +1,743 @@
+/*
+ * 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.room.solver
+
+import androidx.room.RoomKspProcessor
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.compiler.TestCompilationArguments
+import androidx.room.compiler.processing.util.compiler.compile
+import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.processor.Context.BooleanProcessorOptions.USE_NULL_AWARE_CONVERTER
+import androidx.room.processor.CustomConverterProcessor
+import androidx.room.processor.DaoProcessor
+import androidx.room.solver.types.CustomTypeConverterWrapper
+import androidx.room.solver.types.TypeConverter
+import androidx.room.testing.context
+import androidx.room.vo.BuiltInConverterFlags
+import androidx.room.writer.DaoWriter
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.tools.Diagnostic
+
+@RunWith(JUnit4::class)
+class NullabilityAwareTypeConverterStoreTest {
+    @get:Rule
+    val tmpFolder = TemporaryFolder()
+    val source = Source.kotlin(
+        "Foo.kt",
+        """
+            import androidx.room.*
+            class MyClass
+            class NonNullConverters {
+                @TypeConverter
+                fun myClassToString(myClass: MyClass): String {
+                    TODO()
+                }
+                @TypeConverter
+                fun stringToMyClass(input: String): MyClass {
+                    TODO()
+                }
+            }
+            class MyNullableReceivingConverters {
+                @TypeConverter
+                fun nullableMyClassToNonNullString(myClass: MyClass?): String {
+                    TODO()
+                }
+                @TypeConverter
+                fun nullableStringToNonNullMyClass(input: String?): MyClass {
+                    TODO()
+                }
+            }
+            class MyFullyNullableConverters {
+                @TypeConverter
+                fun nullableMyClassToNullableString(myClass: MyClass?): String? {
+                    TODO()
+                }
+                @TypeConverter
+                fun nullableStringToNullableMyClass(input: String?): MyClass? {
+                    TODO()
+                }
+            }
+        """.trimIndent()
+    )
+
+    private fun XTestInvocation.createStore(
+        vararg converters: String
+    ): TypeConverterStore {
+        val allConverters = converters.flatMap {
+            CustomConverterProcessor(
+                context = context,
+                element = processingEnv.requireTypeElement(it)
+            ).process()
+        }.map(::CustomTypeConverterWrapper)
+        return TypeAdapterStore.create(
+            context = context,
+            builtInConverterFlags = BuiltInConverterFlags.DEFAULT,
+            allConverters
+        ).typeConverterStore
+    }
+
+    @Test
+    fun withNonNullableConverters() {
+        val result = collectStringConversionResults("NonNullConverters")
+        assertResult(
+            result.trim(),
+            """
+            JAVAC
+            String? to MyClass?: stringToMyClass
+            MyClass? to String?: myClassToString
+            String? to MyClass!: stringToMyClass
+            MyClass! to String?: myClassToString
+            String! to MyClass?: stringToMyClass
+            MyClass? to String!: myClassToString
+            String! to MyClass!: stringToMyClass
+            MyClass! to String!: myClassToString
+            KSP
+            String? to MyClass?: (String? == null ? null : stringToMyClass)
+            MyClass? to String?: (MyClass? == null ? null : myClassToString)
+            String? to MyClass!: null
+            MyClass! to String?: myClassToString / (String! as String?)
+            String! to MyClass?: stringToMyClass / (MyClass! as MyClass?)
+            MyClass? to String!: null
+            String! to MyClass!: stringToMyClass
+            MyClass! to String!: myClassToString
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun withNonNullableConverters_cursor() {
+        val result = collectCursorResults("NonNullConverters")
+        assertResult(
+            result.trim(),
+            """
+                JAVAC
+                Cursor to MyClass?: stringToMyClass
+                MyClass? to Cursor: myClassToString
+                Cursor to MyClass!: stringToMyClass
+                MyClass! to Cursor: myClassToString
+                KSP
+                Cursor to MyClass?: (String? == null ? null : stringToMyClass)
+                MyClass? to Cursor: (MyClass? == null ? null : myClassToString)
+                // when reading from cursor, we can assume non-null cursor value when
+                // we don't have a converter that would convert it from String?
+                Cursor to MyClass!: stringToMyClass
+                MyClass! to Cursor: myClassToString
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun withNonNullAndNullableReceiving() {
+        val result = collectStringConversionResults(
+            "NonNullConverters",
+            "MyNullableReceivingConverters"
+        )
+        assertResult(
+            result.trim(),
+            """
+            JAVAC
+            String? to MyClass?: stringToMyClass
+            MyClass? to String?: myClassToString
+            String? to MyClass!: stringToMyClass
+            MyClass! to String?: myClassToString
+            String! to MyClass?: stringToMyClass
+            MyClass? to String!: myClassToString
+            String! to MyClass!: stringToMyClass
+            MyClass! to String!: myClassToString
+            KSP
+            String? to MyClass?: nullableStringToNonNullMyClass / (MyClass! as MyClass?)
+            MyClass? to String?: nullableMyClassToNonNullString / (String! as String?)
+            String? to MyClass!: nullableStringToNonNullMyClass
+            MyClass! to String?: myClassToString / (String! as String?)
+            String! to MyClass?: stringToMyClass / (MyClass! as MyClass?)
+            MyClass? to String!: nullableMyClassToNonNullString
+            String! to MyClass!: stringToMyClass
+            MyClass! to String!: myClassToString
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun withNonNullAndNullableReceiving_cursor() {
+        val result = collectCursorResults(
+            "NonNullConverters",
+            "MyNullableReceivingConverters"
+        )
+        assertResult(
+            result.trim(),
+            """
+            JAVAC
+            Cursor to MyClass?: stringToMyClass
+            MyClass? to Cursor: myClassToString
+            Cursor to MyClass!: stringToMyClass
+            MyClass! to Cursor: myClassToString
+            KSP
+            // we start from nullable string because cursor values are assumed nullable when reading
+            Cursor to MyClass?: nullableStringToNonNullMyClass / (MyClass! as MyClass?)
+            // there is an additional upcast for String! to String? because when the written value
+            // is nullable, we prioritize a nullable column
+            MyClass? to Cursor: nullableMyClassToNonNullString / (String! as String?)
+            Cursor to MyClass!: nullableStringToNonNullMyClass
+            MyClass! to Cursor: myClassToString
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun withFullyNullableConverters() {
+        val result = collectStringConversionResults(
+            "NonNullConverters",
+            "MyNullableReceivingConverters",
+            "MyFullyNullableConverters"
+        )
+        assertResult(
+            result.trim(),
+            """
+                JAVAC
+                String? to MyClass?: stringToMyClass
+                MyClass? to String?: myClassToString
+                String? to MyClass!: stringToMyClass
+                MyClass! to String?: myClassToString
+                String! to MyClass?: stringToMyClass
+                MyClass? to String!: myClassToString
+                String! to MyClass!: stringToMyClass
+                MyClass! to String!: myClassToString
+                KSP
+                String? to MyClass?: nullableStringToNullableMyClass
+                MyClass? to String?: nullableMyClassToNullableString
+                String? to MyClass!: nullableStringToNonNullMyClass
+                // another alternative is to use nonNullMyClassToNullableString and then upcast
+                // both are equal weight
+                MyClass! to String?: (MyClass! as MyClass?) / nullableMyClassToNullableString
+                String! to MyClass?: (String! as String?) / nullableStringToNullableMyClass
+                MyClass? to String!: nullableMyClassToNonNullString
+                String! to MyClass!: stringToMyClass
+                MyClass! to String!: myClassToString
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun withFullyNullableConverters_cursor() {
+        val result = collectCursorResults(
+            "NonNullConverters",
+            "MyNullableReceivingConverters",
+            "MyFullyNullableConverters"
+        )
+        assertResult(
+            result.trim(),
+            """
+                JAVAC
+                Cursor to MyClass?: stringToMyClass
+                MyClass? to Cursor: myClassToString
+                Cursor to MyClass!: stringToMyClass
+                MyClass! to Cursor: myClassToString
+                KSP
+                Cursor to MyClass?: nullableStringToNullableMyClass
+                MyClass? to Cursor: nullableMyClassToNullableString
+                Cursor to MyClass!: nullableStringToNonNullMyClass
+                MyClass! to Cursor: myClassToString
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun pojoProcess() {
+        // This is a repro case from trying to run TestApp with null aware converter.
+        // It reproduces the case where if we don't know nullability, we shouldn't try to
+        // prioritize nullable or non-null; instead YOLO and find whichever we can find first.
+        val user = Source.java(
+            "User", """
+            import androidx.room.*;
+            import java.util.*;
+            @TypeConverters({TestConverters.class})
+            @Entity
+            public class User {
+                @PrimaryKey
+                public int mId;
+                public Set<Day> mWorkDays = new HashSet<>();
+            }
+        """.trimIndent()
+        )
+        val converters = Source.java(
+            "TestConverters", """
+            import androidx.room.*;
+            import java.util.Date;
+            import java.util.HashSet;
+            import java.util.Set;
+            class TestConverters {
+                @TypeConverter
+                public static Set<Day> decomposeDays(int flags) {
+                    Set<Day> result = new HashSet<>();
+                    for (Day day : Day.values()) {
+                        if ((flags & (1 << day.ordinal())) != 0) {
+                            result.add(day);
+                        }
+                    }
+                    return result;
+                }
+
+                @TypeConverter
+                public static int composeDays(Set<Day> days) {
+                    int result = 0;
+                    for (Day day : days) {
+                        result |= 1 << day.ordinal();
+                    }
+                    return result;
+                }
+            }
+        """.trimIndent()
+        )
+        val day = Source.java(
+            "Day", """
+            public enum Day {
+                MONDAY,
+                TUESDAY,
+                WEDNESDAY,
+                THURSDAY,
+                FRIDAY,
+                SATURDAY,
+                SUNDAY
+            }
+        """.trimIndent()
+        )
+        val dao = Source.java(
+            "MyDao", """
+            import androidx.room.*;
+            @Dao
+            interface MyDao {
+                @Insert
+                void insert(User user);
+            }
+        """.trimIndent()
+        )
+        runProcessorTest(
+            sources = listOf(user, day, converters, dao),
+            options = mapOf(
+                USE_NULL_AWARE_CONVERTER.argName to "true"
+            )
+        ) { invocation ->
+            val daoProcessor = DaoProcessor(
+                baseContext = invocation.context,
+                element = invocation.processingEnv.requireTypeElement("MyDao"),
+                dbType = invocation.processingEnv.requireType("androidx.room.RoomDatabase"),
+                dbVerifier = null
+            )
+            DaoWriter(
+                dao = daoProcessor.process(),
+                dbElement = invocation.processingEnv
+                    .requireTypeElement("androidx.room.RoomDatabase"),
+                processingEnv = invocation.processingEnv
+            ).write(invocation.processingEnv)
+            invocation.assertCompilationResult {
+                generatedSourceFileWithPath("MyDao_Impl.java").let {
+                    // make sure it bounded w/o upcasting to Boolean
+                    it.contains("final int _tmp = TestConverters.composeDays(value.mWorkDays);")
+                    it.contains("stmt.bindLong(2, _tmp);")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun checkSyntheticConverters() {
+        class MockTypeConverter(
+            from: XType,
+            to: XType,
+        ) : TypeConverter(
+            from = from,
+            to = to
+        ) {
+            override fun doConvert(
+                inputVarName: String,
+                outputVarName: String,
+                scope: CodeGenScope
+            ) {
+            }
+        }
+        runProcessorTest { invocation ->
+            val string = invocation.processingEnv.requireType(String::class)
+                .makeNonNullable()
+            val int = invocation.processingEnv.requireType(Int::class)
+                .makeNonNullable()
+            val long = invocation.processingEnv.requireType(Long::class)
+                .makeNonNullable()
+            val number = invocation.processingEnv.requireType(Number::class)
+                .makeNonNullable()
+            NullAwareTypeConverterStore(
+                context = invocation.context,
+                typeConverters = listOf(
+                    MockTypeConverter(
+                        from = string.makeNullable(),
+                        to = int.makeNullable()
+                    )
+                ),
+                knownColumnTypes = emptyList()
+            ).let { store ->
+                // nullable converter, don't duplicate anything
+                assertThat(
+                    store.typeConverters
+                ).hasSize(1)
+            }
+            NullAwareTypeConverterStore(
+                context = invocation.context,
+                typeConverters = listOf(
+                    MockTypeConverter(
+                        from = string,
+                        to = int
+                    )
+                ),
+                knownColumnTypes = emptyList()
+            ).let { store ->
+                if (invocation.isKsp) {
+                    // add a null wrapper version
+                    assertThat(store.typeConverters).hasSize(2)
+                } else {
+                    // do not duplicate unless we run in KSP
+                    assertThat(store.typeConverters).hasSize(1)
+                }
+            }
+            NullAwareTypeConverterStore(
+                context = invocation.context,
+                typeConverters = listOf(
+                    MockTypeConverter(
+                        from = string,
+                        to = int
+                    ),
+                    MockTypeConverter(
+                        from = string.makeNullable(),
+                        to = int
+                    )
+                ),
+                knownColumnTypes = emptyList()
+            ).let { store ->
+                // don't duplicate, we already have a null receiving version
+                assertThat(store.typeConverters).hasSize(2)
+            }
+            NullAwareTypeConverterStore(
+                context = invocation.context,
+                typeConverters = listOf(
+                    MockTypeConverter(
+                        from = string,
+                        to = int
+                    ),
+                    MockTypeConverter(
+                        from = string.makeNullable(),
+                        to = int.makeNullable()
+                    )
+                ),
+                knownColumnTypes = emptyList()
+            ).let { store ->
+                // don't duplicate, we already have a null receiving version
+                assertThat(store.typeConverters).hasSize(2)
+            }
+            NullAwareTypeConverterStore(
+                context = invocation.context,
+                typeConverters = listOf(
+                    MockTypeConverter(
+                        from = string,
+                        to = int
+                    ),
+                    MockTypeConverter(
+                        from = string,
+                        to = long
+                    ),
+                    MockTypeConverter(
+                        from = string.makeNullable(),
+                        to = int.makeNullable()
+                    )
+                ),
+                knownColumnTypes = emptyList()
+            ).let { store ->
+                // don't duplicate, we already have a null receiving version
+                if (invocation.isKsp) {
+                    // duplicate the long receiving one
+                    assertThat(store.typeConverters).hasSize(4)
+                } else {
+                    // don't duplicate in javac
+                    assertThat(store.typeConverters).hasSize(3)
+                }
+            }
+            NullAwareTypeConverterStore(
+                context = invocation.context,
+                typeConverters = listOf(
+                    MockTypeConverter(
+                        from = string,
+                        to = number
+                    ),
+                    MockTypeConverter(
+                        from = string.makeNullable(),
+                        to = int
+                    ),
+                ),
+                knownColumnTypes = emptyList()
+            ).let { store ->
+                // don't duplicate string number converter since we have string? to int
+                assertThat(store.typeConverters).hasSize(2)
+            }
+            NullAwareTypeConverterStore(
+                context = invocation.context,
+                typeConverters = listOf(
+                    MockTypeConverter(
+                        from = string,
+                        to = number.makeNullable()
+                    ),
+                    MockTypeConverter(
+                        from = string.makeNullable(),
+                        to = int
+                    ),
+                ),
+                knownColumnTypes = emptyList()
+            ).let { store ->
+                // don't duplicate string number converter since we have string? to int
+                assertThat(store.typeConverters).hasSize(2)
+            }
+        }
+    }
+
+    @Test
+    fun warnIfTurnedOffInKsp() {
+        val sources = Source.kotlin("Foo.kt", "")
+        arrayOf("", "true", "false").forEach { value ->
+            val result = compile(
+                workingDir = tmpFolder.newFolder(),
+                arguments = TestCompilationArguments(
+                    sources = listOf(sources),
+                    symbolProcessorProviders = listOf(
+                        RoomKspProcessor.Provider()
+                    ),
+                    processorOptions = mapOf(
+                        USE_NULL_AWARE_CONVERTER.argName to value
+                    )
+                )
+            )
+            val warnings = result.diagnostics[Diagnostic.Kind.WARNING]?.map {
+                it.msg
+            }?.filter {
+                it.contains("Disabling null-aware type analysis in KSP is a temporary flag")
+            } ?: emptyList()
+            val expected = if (value == "false") {
+                1
+            } else {
+                0
+            }
+            assertThat(
+                warnings
+            ).hasSize(expected)
+        }
+    }
+
+    /**
+     * Test converting a known column type into another type due to explicit affinity
+     */
+    @Test
+    fun knownColumnTypeToExplicitType() {
+        val source = Source.kotlin(
+            "Subject.kt", """
+            import androidx.room.*
+            object MyByteArrayConverter {
+                @TypeConverter
+                fun toByteArray(input:String): ByteArray { TODO() }
+                @TypeConverter
+                fun fromByteArray(input:ByteArray): String { TODO() }
+            }
+            class Subject(val arr:ByteArray)
+        """.trimIndent()
+        )
+        runProcessorTest(
+            sources = listOf(source),
+            options = mapOf(
+                USE_NULL_AWARE_CONVERTER.argName to "true"
+            )
+        ) { invocation ->
+            val byteArray = invocation.processingEnv.requireTypeElement("Subject")
+                .getDeclaredFields().first().type.makeNonNullable()
+            val string = invocation.processingEnv.requireType("java.lang.String")
+            invocation.createStore().let { storeWithoutConverter ->
+                val intoStatement = storeWithoutConverter.findConverterIntoStatement(
+                    input = byteArray,
+                    columnTypes = listOf(
+                        string.makeNullable(),
+                        string.makeNonNullable()
+                    )
+                )
+                assertThat(intoStatement).isNull()
+                val fromCursor = storeWithoutConverter.findConverterFromCursor(
+                    output = byteArray,
+                    columnTypes = listOf(
+                        string.makeNullable(),
+                        string.makeNonNullable()
+                    )
+                )
+                assertThat(fromCursor).isNull()
+            }
+
+            invocation.createStore(
+                "MyByteArrayConverter"
+            ).let { storeWithConverter ->
+                val intoStatement = storeWithConverter.findConverterIntoStatement(
+                    input = byteArray,
+                    columnTypes = listOf(
+                        string.makeNullable(),
+                        string.makeNonNullable()
+                    )
+                )
+                assertThat(intoStatement?.toSignature()).isEqualTo("fromByteArray")
+                assertThat(intoStatement?.to).isEqualTo(string.makeNonNullable())
+                assertThat(intoStatement?.from).isEqualTo(byteArray.makeNonNullable())
+                val fromCursor = storeWithConverter.findConverterFromCursor(
+                    output = byteArray,
+                    columnTypes = listOf(
+                        string.makeNullable(),
+                        string.makeNonNullable()
+                    )
+                )
+                assertThat(fromCursor?.toSignature()).isEqualTo("toByteArray")
+                assertThat(fromCursor?.to).isEqualTo(byteArray.makeNonNullable())
+                assertThat(fromCursor?.from).isEqualTo(string.makeNonNullable())
+            }
+        }
+    }
+
+    /**
+     * Collect results for conversion from String to our type
+     */
+    private fun collectStringConversionResults(
+        vararg selectedConverters: String
+    ): String {
+        val result = StringBuilder()
+        runProcessorTest(
+            sources = listOf(source),
+            options = mapOf(
+                USE_NULL_AWARE_CONVERTER.argName to "true"
+            )
+        ) { invocation ->
+            val store = invocation.createStore(*selectedConverters)
+            assertThat(store).isInstanceOf(NullAwareTypeConverterStore::class.java)
+            val myClassTypeElement = invocation.processingEnv.requireTypeElement(
+                "MyClass"
+            )
+            val stringTypeElement = invocation.processingEnv.requireTypeElement(
+                "java.lang.String"
+            )
+
+            result.appendLine(invocation.processingEnv.backend.name)
+            listOf(
+                stringTypeElement.type.makeNullable(),
+                stringTypeElement.type.makeNonNullable(),
+            ).forEach { stringType ->
+                listOf(
+                    myClassTypeElement.type.makeNullable(),
+                    myClassTypeElement.type.makeNonNullable()
+                ).forEach { myClassType ->
+                    val fromString = store.findTypeConverter(
+                        input = stringType,
+                        output = myClassType
+                    )
+                    val toString = store.findTypeConverter(
+                        input = myClassType,
+                        output = stringType
+                    )
+                    result.apply {
+                        append(stringType.toSignature())
+                        append(" to ")
+                        append(myClassType.toSignature())
+                        append(": ")
+                        appendLine(fromString?.toSignature() ?: "null")
+                    }
+
+                    result.apply {
+                        append(myClassType.toSignature())
+                        append(" to ")
+                        append(stringType.toSignature())
+                        append(": ")
+                        appendLine(toString?.toSignature() ?: "null")
+                    }
+                }
+            }
+        }
+        return result.toString()
+    }
+
+    /**
+     * Collect results for conversion from an unknown cursor type to our type
+     */
+    private fun collectCursorResults(
+        vararg selectedConverters: String
+    ): String {
+        val result = StringBuilder()
+        runProcessorTest(
+            sources = listOf(source),
+            options = mapOf(
+                USE_NULL_AWARE_CONVERTER.argName to "true"
+            )
+        ) { invocation ->
+            val store = invocation.createStore(*selectedConverters)
+            assertThat(store).isInstanceOf(NullAwareTypeConverterStore::class.java)
+            val myClassTypeElement = invocation.processingEnv.requireTypeElement(
+                "MyClass"
+            )
+
+            result.appendLine(invocation.processingEnv.backend.name)
+            listOf(
+                myClassTypeElement.type.makeNullable(),
+                myClassTypeElement.type.makeNonNullable()
+            ).forEach { myClassType ->
+                val toMyClass = store.findConverterFromCursor(
+                    columnTypes = null,
+                    output = myClassType
+                )
+                val fromMyClass = store.findConverterIntoStatement(
+                    input = myClassType,
+                    columnTypes = null
+                )
+
+                result.apply {
+                    append("Cursor to ")
+                    append(myClassType.toSignature())
+                    append(": ")
+                    appendLine(toMyClass?.toSignature() ?: "null")
+                }
+
+                result.apply {
+                    append(myClassType.toSignature())
+                    append(" to Cursor: ")
+                    appendLine(fromMyClass?.toSignature() ?: "null")
+                }
+            }
+        }
+        return result.toString()
+    }
+
+    private fun assertResult(result: String, expected: String) {
+        // remove commented lines from expected as they are used to explain cases for test's
+        // readability
+        assertThat(result).isEqualTo(
+            expected
+                .lines()
+                .filterNot { it.trim().startsWith("//") }
+                .joinToString("\n")
+        )
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/Signatures.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/Signatures.kt
new file mode 100644
index 0000000..4ddee69
--- /dev/null
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/Signatures.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.room.solver
+
+import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.processing.XType
+import androidx.room.solver.types.CompositeTypeConverter
+import androidx.room.solver.types.CustomTypeConverterWrapper
+import androidx.room.solver.types.NullSafeTypeConverter
+import androidx.room.solver.types.RequireNotNullTypeConverter
+import androidx.room.solver.types.TypeConverter
+import androidx.room.solver.types.UpCastTypeConverter
+
+// Shared signatures for objects that make testing more readable
+private fun XNullability.toSignature() = when (this) {
+    XNullability.NONNULL -> "!"
+    XNullability.NULLABLE -> "?"
+    XNullability.UNKNOWN -> ""
+}
+
+fun XType.toSignature() =
+    "$typeName${nullability.toSignature()}".substringAfter("java.lang.")
+
+fun TypeConverter.toSignature(): String {
+    return when (this) {
+        is CompositeTypeConverter -> "${conv1.toSignature()} / ${conv2.toSignature()}"
+        is CustomTypeConverterWrapper -> this.custom.methodName
+        is NullSafeTypeConverter ->
+            "(${this.from.toSignature()} == null " +
+                "? null : ${this.delegate.toSignature()})"
+        is RequireNotNullTypeConverter ->
+            "checkNotNull(${from.toSignature()})"
+        is UpCastTypeConverter ->
+            "(${from.toSignature()} as ${to.toSignature()})"
+        else -> "${from.toSignature()} -> ${to.toSignature()}"
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterCostTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterCostTest.kt
new file mode 100644
index 0000000..03ab939
--- /dev/null
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterCostTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.room.solver
+
+import androidx.room.solver.types.TypeConverter.Cost
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class TypeConverterCostTest {
+    @Test
+    fun sections() {
+        val cost = Cost(
+            upCasts = 7,
+            nullSafeWrapper = 3,
+            converters = 1,
+            requireNotNull = 2
+        )
+        assertThat(
+            cost.toString()
+        ).isEqualTo(
+            "Cost[upcast:7,nullsafe:3,converters:1,requireNotNull:2]"
+        )
+    }
+
+    @Test
+    fun sum() {
+        assertThat(
+            Cost(converters = 3) + Cost(converters = 3, nullSafeWrapper = 4)
+
+        ).isEqualTo(Cost(converters = 6, nullSafeWrapper = 4))
+    }
+
+    @Test
+    fun sum2() {
+        val sum = Cost(
+            converters = 1,
+            upCasts = 60,
+        ) + Cost(
+            converters = 1,
+            nullSafeWrapper = 2,
+            upCasts = 20
+        )
+        assertThat(
+            sum
+        ).isEqualTo(
+            Cost(
+               converters = 2,
+               nullSafeWrapper = 2,
+               upCasts = 80
+            )
+        )
+    }
+
+    @Test
+    fun compare() {
+        assertThat(
+            Cost(requireNotNull = 1, converters = 2, nullSafeWrapper = 3, upCasts = 4)
+        ).isEquivalentAccordingToCompareTo(
+            Cost(requireNotNull = 1, converters = 2, nullSafeWrapper = 3, upCasts = 4)
+        )
+        // require not null is the most expensive
+        assertThat(
+            Cost(requireNotNull = 2, converters = 1, nullSafeWrapper = 1, upCasts = 1)
+        ).isGreaterThan(
+            Cost(requireNotNull = 1, converters = 2, nullSafeWrapper = 2, upCasts = 2)
+        )
+        // converters are the second most expensive
+        assertThat(
+            Cost(requireNotNull = 1, converters = 2, nullSafeWrapper = 1, upCasts = 1)
+        ).isGreaterThan(
+            Cost(requireNotNull = 1, converters = 1, nullSafeWrapper = 2, upCasts = 2)
+        )
+        // null safe wrapper is the third most expensive
+        assertThat(
+            Cost(requireNotNull = 1, converters = 1, nullSafeWrapper = 2, upCasts = 1)
+        ).isGreaterThan(
+            Cost(requireNotNull = 1, converters = 1, nullSafeWrapper = 1, upCasts = 2)
+        )
+        // upcast is the least expensive
+        assertThat(
+            Cost(requireNotNull = 1, converters = 1, nullSafeWrapper = 1, upCasts = 2)
+        ).isGreaterThan(
+            Cost(requireNotNull = 1, converters = 1, nullSafeWrapper = 1, upCasts = 1)
+        )
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
index 18c67d2..a6c6499 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeConverterStoreTest.kt
@@ -98,6 +98,14 @@
                 findConverter("Type1_Super", "Type2_Super")
             ).isEqualTo(
                 "Type1_Super -> JumpType_2 : JumpType_2 -> JumpType_3 : JumpType_3 -> Type2_Sub"
+                    .let { tillSub ->
+                        if (invocation.context.useNullAwareConverter) {
+                            // new type converter will have a step for upcasts too
+                            "$tillSub : Type2_Sub -> Type2_Super"
+                        } else {
+                            tillSub
+                        }
+                    }
             )
             assertThat(
                 findConverter("Type1", "Type2_Sub")
@@ -107,7 +115,14 @@
             assertThat(
                 findConverter("Type1_Sub", "Type2_Sub")
             ).isEqualTo(
-                "Type1 -> JumpType_1 : JumpType_1 -> Type2_Sub"
+                "Type1 -> JumpType_1 : JumpType_1 -> Type2_Sub".let { end ->
+                    if (invocation.context.useNullAwareConverter) {
+                        // new type converter will have a step for upcasts too
+                        "Type1_Sub -> Type1 : $end"
+                    } else {
+                        end
+                    }
+                }
             )
             assertThat(
                 findConverter("Type2", "Type2_Sub")
diff --git a/room/room-guava/api/2.4.0-beta02.txt b/room/room-guava/api/2.4.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/room-guava/api/2.4.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/room-guava/api/public_plus_experimental_2.4.0-beta02.txt b/room/room-guava/api/public_plus_experimental_2.4.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/room-guava/api/public_plus_experimental_2.4.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/room-guava/api/res-2.4.0-beta02.txt b/room/room-guava/api/res-2.4.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/room-guava/api/res-2.4.0-beta02.txt
diff --git a/room/room-guava/api/restricted_2.4.0-beta02.txt b/room/room-guava/api/restricted_2.4.0-beta02.txt
new file mode 100644
index 0000000..1c04602
--- /dev/null
+++ b/room/room-guava/api/restricted_2.4.0-beta02.txt
@@ -0,0 +1,14 @@
+// Signature format: 4.0
+package androidx.room.guava {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class GuavaRoom {
+    method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+    method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(androidx.room.RoomDatabase!, java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+    method public static <T> com.google.common.util.concurrent.ListenableFuture<T!>! createListenableFuture(androidx.room.RoomDatabase!, boolean, java.util.concurrent.Callable<T!>!, androidx.room.RoomSQLiteQuery!, boolean);
+    method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, boolean, java.util.concurrent.Callable<T!>, androidx.room.RoomSQLiteQuery, boolean, android.os.CancellationSignal?);
+    method @Deprecated public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, java.util.concurrent.Callable<T!>);
+    method public static <T> com.google.common.util.concurrent.ListenableFuture<T!> createListenableFuture(androidx.room.RoomDatabase, boolean, java.util.concurrent.Callable<T!>);
+  }
+
+}
+
diff --git a/room/room-ktx/api/2.4.0-beta02.txt b/room/room-ktx/api/2.4.0-beta02.txt
new file mode 100644
index 0000000..418c567
--- /dev/null
+++ b/room/room-ktx/api/2.4.0-beta02.txt
@@ -0,0 +1,20 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public final class CoroutinesRoomKt {
+  }
+
+  public final class RoomDatabaseKt {
+    method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+  }
+
+}
+
+package androidx.room.migration {
+
+  public final class MigrationKt {
+    method public static androidx.room.migration.Migration Migration(int startVersion, int endVersion, kotlin.jvm.functions.Function1<? super androidx.sqlite.db.SupportSQLiteDatabase,kotlin.Unit> migrate);
+  }
+
+}
+
diff --git a/room/room-ktx/api/public_plus_experimental_2.4.0-beta02.txt b/room/room-ktx/api/public_plus_experimental_2.4.0-beta02.txt
new file mode 100644
index 0000000..418c567
--- /dev/null
+++ b/room/room-ktx/api/public_plus_experimental_2.4.0-beta02.txt
@@ -0,0 +1,20 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public final class CoroutinesRoomKt {
+  }
+
+  public final class RoomDatabaseKt {
+    method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+  }
+
+}
+
+package androidx.room.migration {
+
+  public final class MigrationKt {
+    method public static androidx.room.migration.Migration Migration(int startVersion, int endVersion, kotlin.jvm.functions.Function1<? super androidx.sqlite.db.SupportSQLiteDatabase,kotlin.Unit> migrate);
+  }
+
+}
+
diff --git a/room/room-ktx/api/res-2.4.0-beta02.txt b/room/room-ktx/api/res-2.4.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/room-ktx/api/res-2.4.0-beta02.txt
diff --git a/room/room-ktx/api/restricted_2.4.0-beta02.txt b/room/room-ktx/api/restricted_2.4.0-beta02.txt
new file mode 100644
index 0000000..30647b2
--- /dev/null
+++ b/room/room-ktx/api/restricted_2.4.0-beta02.txt
@@ -0,0 +1,33 @@
+// Signature format: 4.0
+package androidx.room {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class CoroutinesRoom {
+    method public static <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
+    method public static suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
+    method public static suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, android.os.CancellationSignal cancellationSignal, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
+    field public static final androidx.room.CoroutinesRoom.Companion Companion;
+  }
+
+  public static final class CoroutinesRoom.Companion {
+    method public <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String![] tableNames, java.util.concurrent.Callable<R> callable);
+    method public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
+    method public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, android.os.CancellationSignal cancellationSignal, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R> p);
+  }
+
+  public final class CoroutinesRoomKt {
+  }
+
+  public final class RoomDatabaseKt {
+    method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> p);
+  }
+
+}
+
+package androidx.room.migration {
+
+  public final class MigrationKt {
+    method public static androidx.room.migration.Migration Migration(int startVersion, int endVersion, kotlin.jvm.functions.Function1<? super androidx.sqlite.db.SupportSQLiteDatabase,kotlin.Unit> migrate);
+  }
+
+}
+
diff --git a/room/room-migration/api/2.4.0-beta02.txt b/room/room-migration/api/2.4.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/room-migration/api/2.4.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/room-migration/api/public_plus_experimental_2.4.0-beta02.txt b/room/room-migration/api/public_plus_experimental_2.4.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/room-migration/api/public_plus_experimental_2.4.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/room-migration/api/restricted_2.4.0-beta02.txt b/room/room-migration/api/restricted_2.4.0-beta02.txt
new file mode 100644
index 0000000..50f8227
--- /dev/null
+++ b/room/room-migration/api/restricted_2.4.0-beta02.txt
@@ -0,0 +1,110 @@
+// Signature format: 4.0
+package androidx.room.migration.bundle {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class BundleUtil {
+    field public static final String TABLE_NAME_PLACEHOLDER = "${TABLE_NAME}";
+    field public static final String VIEW_NAME_PLACEHOLDER = "${VIEW_NAME}";
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DatabaseBundle {
+    ctor public DatabaseBundle(int, String!, java.util.List<androidx.room.migration.bundle.EntityBundle!>!, java.util.List<androidx.room.migration.bundle.DatabaseViewBundle!>!, java.util.List<java.lang.String!>!);
+    ctor public DatabaseBundle();
+    method public java.util.List<java.lang.String!>! buildCreateQueries();
+    method public java.util.List<androidx.room.migration.bundle.EntityBundle!>! getEntities();
+    method public java.util.Map<java.lang.String!,androidx.room.migration.bundle.EntityBundle!>! getEntitiesByTableName();
+    method public String! getIdentityHash();
+    method public int getVersion();
+    method public java.util.List<androidx.room.migration.bundle.DatabaseViewBundle!>! getViews();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.DatabaseBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DatabaseViewBundle {
+    ctor public DatabaseViewBundle(String!, String!);
+    method public String! createView();
+    method public String! getCreateSql();
+    method public String! getViewName();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.DatabaseViewBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class EntityBundle {
+    ctor public EntityBundle(String!, String!, java.util.List<androidx.room.migration.bundle.FieldBundle!>!, androidx.room.migration.bundle.PrimaryKeyBundle!, java.util.List<androidx.room.migration.bundle.IndexBundle!>!, java.util.List<androidx.room.migration.bundle.ForeignKeyBundle!>!);
+    method public java.util.Collection<java.lang.String!>! buildCreateQueries();
+    method public String! createNewTable();
+    method public String! createTable();
+    method public String! getCreateSql();
+    method public java.util.List<androidx.room.migration.bundle.FieldBundle!>! getFields();
+    method public java.util.Map<java.lang.String!,androidx.room.migration.bundle.FieldBundle!>! getFieldsByColumnName();
+    method public java.util.List<androidx.room.migration.bundle.ForeignKeyBundle!>! getForeignKeys();
+    method public java.util.List<androidx.room.migration.bundle.IndexBundle!>! getIndices();
+    method public String! getNewTableName();
+    method public androidx.room.migration.bundle.PrimaryKeyBundle! getPrimaryKey();
+    method public String! getTableName();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.EntityBundle!);
+    method public String renameToOriginal();
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FieldBundle {
+    ctor @Deprecated public FieldBundle(String!, String!, String!, boolean);
+    ctor public FieldBundle(String!, String!, String!, boolean, String!);
+    method public String! getAffinity();
+    method public String! getColumnName();
+    method public String! getDefaultValue();
+    method public String! getFieldPath();
+    method public boolean isNonNull();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.FieldBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ForeignKeyBundle {
+    ctor public ForeignKeyBundle(String!, String!, String!, java.util.List<java.lang.String!>!, java.util.List<java.lang.String!>!);
+    method public java.util.List<java.lang.String!>! getColumns();
+    method public String! getOnDelete();
+    method public String! getOnUpdate();
+    method public java.util.List<java.lang.String!>! getReferencedColumns();
+    method public String! getTable();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.ForeignKeyBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsEntityBundle extends androidx.room.migration.bundle.EntityBundle {
+    ctor public FtsEntityBundle(String!, String!, java.util.List<androidx.room.migration.bundle.FieldBundle!>!, androidx.room.migration.bundle.PrimaryKeyBundle!, String!, androidx.room.migration.bundle.FtsOptionsBundle!, java.util.List<java.lang.String!>!);
+    method public androidx.room.migration.bundle.FtsOptionsBundle! getFtsOptions();
+    method public java.util.List<java.lang.String!>! getShadowTableNames();
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsOptionsBundle {
+    ctor public FtsOptionsBundle(String!, java.util.List<java.lang.String!>!, String!, String!, String!, java.util.List<java.lang.String!>!, java.util.List<java.lang.Integer!>!, String!);
+    method public String! getContentTable();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.FtsOptionsBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class IndexBundle {
+    ctor @Deprecated public IndexBundle(String!, boolean, java.util.List<java.lang.String!>!, String!);
+    ctor public IndexBundle(String!, boolean, java.util.List<java.lang.String!>!, java.util.List<java.lang.String!>!, String!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! create(String);
+    method public java.util.List<java.lang.String!>! getColumnNames();
+    method public String! getCreateSql(String!);
+    method public String! getName();
+    method public java.util.List<java.lang.String!>! getOrders();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.IndexBundle);
+    method public boolean isUnique();
+    field public static final String DEFAULT_PREFIX = "index_";
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class PrimaryKeyBundle {
+    ctor public PrimaryKeyBundle(boolean, java.util.List<java.lang.String!>!);
+    method public java.util.List<java.lang.String!>! getColumnNames();
+    method public boolean isAutoGenerate();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.PrimaryKeyBundle!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SchemaBundle {
+    ctor public SchemaBundle(int, androidx.room.migration.bundle.DatabaseBundle!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.room.migration.bundle.SchemaBundle deserialize(java.io.InputStream!) throws java.io.UnsupportedEncodingException;
+    method public androidx.room.migration.bundle.DatabaseBundle! getDatabase();
+    method public int getFormatVersion();
+    method public boolean isSchemaEqual(androidx.room.migration.bundle.SchemaBundle!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void serialize(androidx.room.migration.bundle.SchemaBundle!, java.io.File!) throws java.io.IOException;
+    field public static final int LATEST_FORMAT = 1; // 0x1
+  }
+
+}
+
diff --git a/room/room-paging/api/2.4.0-beta02.txt b/room/room-paging/api/2.4.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/room-paging/api/2.4.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/room-paging/api/public_plus_experimental_2.4.0-beta02.txt b/room/room-paging/api/public_plus_experimental_2.4.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/room-paging/api/public_plus_experimental_2.4.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/room-paging/api/res-2.4.0-beta02.txt b/room/room-paging/api/res-2.4.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/room-paging/api/res-2.4.0-beta02.txt
diff --git a/room/room-paging/api/restricted_2.4.0-beta02.txt b/room/room-paging/api/restricted_2.4.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/room/room-paging/api/restricted_2.4.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/room/room-runtime/api/2.4.0-beta02.txt b/room/room-runtime/api/2.4.0-beta02.txt
new file mode 100644
index 0000000..c6616ad
--- /dev/null
+++ b/room/room-runtime/api/2.4.0-beta02.txt
@@ -0,0 +1,143 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class DatabaseConfiguration {
+    method public boolean isMigrationRequired(int, int);
+    method @Deprecated public boolean isMigrationRequiredFrom(int);
+    field public final boolean allowDestructiveMigrationOnDowngrade;
+    field public final boolean allowMainThreadQueries;
+    field public final java.util.List<androidx.room.migration.AutoMigrationSpec!> autoMigrationSpecs;
+    field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
+    field public final android.content.Context context;
+    field public final String? copyFromAssetPath;
+    field public final java.io.File? copyFromFile;
+    field public final java.util.concurrent.Callable<java.io.InputStream!>? copyFromInputStream;
+    field public final androidx.room.RoomDatabase.JournalMode! journalMode;
+    field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
+    field public final boolean multiInstanceInvalidation;
+    field public final String? name;
+    field public final androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
+    field public final java.util.concurrent.Executor queryExecutor;
+    field public final boolean requireMigration;
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    field public final java.util.concurrent.Executor transactionExecutor;
+    field public final java.util.List<java.lang.Object!> typeConverters;
+  }
+
+  public class InvalidationTracker {
+    method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
+    method public void refreshVersionsAsync();
+    method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer);
+  }
+
+  public abstract static class InvalidationTracker.Observer {
+    ctor protected InvalidationTracker.Observer(String, java.lang.String!...);
+    ctor public InvalidationTracker.Observer(String![]);
+    method public abstract void onInvalidated(java.util.Set<java.lang.String!>);
+  }
+
+  public class Room {
+    ctor @Deprecated public Room();
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> databaseBuilder(android.content.Context, Class<T!>, String);
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> inMemoryDatabaseBuilder(android.content.Context, Class<T!>);
+    field public static final String MASTER_TABLE_NAME = "room_master_table";
+  }
+
+  public abstract class RoomDatabase {
+    ctor public RoomDatabase();
+    method @Deprecated public void beginTransaction();
+    method @WorkerThread public abstract void clearAllTables();
+    method public void close();
+    method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String);
+    method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
+    method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration!);
+    method @Deprecated public void endTransaction();
+    method public androidx.room.InvalidationTracker getInvalidationTracker();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
+    method public java.util.concurrent.Executor getQueryExecutor();
+    method public java.util.concurrent.Executor getTransactionExecutor();
+    method public <T> T? getTypeConverter(Class<T!>);
+    method public boolean inTransaction();
+    method @CallSuper public void init(androidx.room.DatabaseConfiguration);
+    method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public boolean isOpen();
+    method public android.database.Cursor query(String, Object![]?);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+    method public void runInTransaction(Runnable);
+    method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+    method @Deprecated public void setTransactionSuccessful();
+    field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
+  }
+
+  public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+    method public androidx.room.RoomDatabase.Builder<T!> addAutoMigrationSpec(androidx.room.migration.AutoMigrationSpec);
+    method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
+    method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
+    method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
+    method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+    method public T build();
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> enableMultiInstanceInvalidation();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigration();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
+    method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
+    method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setTransactionExecutor(java.util.concurrent.Executor);
+  }
+
+  public abstract static class RoomDatabase.Callback {
+    ctor public RoomDatabase.Callback();
+    method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onDestructiveMigration(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public enum RoomDatabase.JournalMode {
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
+    enum_constant @RequiresApi(android.os.Build.VERSION_CODES.JELLY_BEAN) public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+  }
+
+  public static class RoomDatabase.MigrationContainer {
+    ctor public RoomDatabase.MigrationContainer();
+    method public void addMigrations(androidx.room.migration.Migration!...);
+    method public void addMigrations(java.util.List<androidx.room.migration.Migration!>);
+    method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+    method public java.util.Map<java.lang.Integer!,java.util.Map<java.lang.Integer!,androidx.room.migration.Migration!>!> getMigrations();
+  }
+
+  public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+    ctor public RoomDatabase.PrepackagedDatabaseCallback();
+    method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public static interface RoomDatabase.QueryCallback {
+    method public void onQuery(String, java.util.List<java.lang.Object!>);
+  }
+
+}
+
+package androidx.room.migration {
+
+  public interface AutoMigrationSpec {
+    method public default void onPostMigrate(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public abstract class Migration {
+    ctor public Migration(int, int);
+    method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
+    field public final int endVersion;
+    field public final int startVersion;
+  }
+
+}
+
diff --git a/room/room-runtime/api/public_plus_experimental_2.4.0-beta02.txt b/room/room-runtime/api/public_plus_experimental_2.4.0-beta02.txt
new file mode 100644
index 0000000..8484f3c
--- /dev/null
+++ b/room/room-runtime/api/public_plus_experimental_2.4.0-beta02.txt
@@ -0,0 +1,153 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class DatabaseConfiguration {
+    method public boolean isMigrationRequired(int, int);
+    method @Deprecated public boolean isMigrationRequiredFrom(int);
+    field public final boolean allowDestructiveMigrationOnDowngrade;
+    field public final boolean allowMainThreadQueries;
+    field public final java.util.List<androidx.room.migration.AutoMigrationSpec!> autoMigrationSpecs;
+    field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
+    field public final android.content.Context context;
+    field public final String? copyFromAssetPath;
+    field public final java.io.File? copyFromFile;
+    field public final java.util.concurrent.Callable<java.io.InputStream!>? copyFromInputStream;
+    field public final androidx.room.RoomDatabase.JournalMode! journalMode;
+    field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
+    field public final boolean multiInstanceInvalidation;
+    field public final String? name;
+    field public final androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
+    field public final java.util.concurrent.Executor queryExecutor;
+    field public final boolean requireMigration;
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    field public final java.util.concurrent.Executor transactionExecutor;
+    field public final java.util.List<java.lang.Object!> typeConverters;
+  }
+
+  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD}) public @interface ExperimentalRoomApi {
+  }
+
+  public class InvalidationTracker {
+    method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
+    method public void refreshVersionsAsync();
+    method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer);
+  }
+
+  public abstract static class InvalidationTracker.Observer {
+    ctor protected InvalidationTracker.Observer(String, java.lang.String!...);
+    ctor public InvalidationTracker.Observer(String![]);
+    method public abstract void onInvalidated(java.util.Set<java.lang.String!>);
+  }
+
+  @androidx.room.ExperimentalRoomApi public class MultiInstanceInvalidationService extends android.app.Service {
+    ctor public MultiInstanceInvalidationService();
+    method public android.os.IBinder? onBind(android.content.Intent);
+  }
+
+  public class Room {
+    ctor @Deprecated public Room();
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> databaseBuilder(android.content.Context, Class<T!>, String);
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> inMemoryDatabaseBuilder(android.content.Context, Class<T!>);
+    field public static final String MASTER_TABLE_NAME = "room_master_table";
+  }
+
+  public abstract class RoomDatabase {
+    ctor public RoomDatabase();
+    method @Deprecated public void beginTransaction();
+    method @WorkerThread public abstract void clearAllTables();
+    method public void close();
+    method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String);
+    method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
+    method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration!);
+    method @Deprecated public void endTransaction();
+    method public androidx.room.InvalidationTracker getInvalidationTracker();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
+    method public java.util.concurrent.Executor getQueryExecutor();
+    method public java.util.concurrent.Executor getTransactionExecutor();
+    method public <T> T? getTypeConverter(Class<T!>);
+    method public boolean inTransaction();
+    method @CallSuper public void init(androidx.room.DatabaseConfiguration);
+    method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public boolean isOpen();
+    method public android.database.Cursor query(String, Object![]?);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+    method public void runInTransaction(Runnable);
+    method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+    method @Deprecated public void setTransactionSuccessful();
+    field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
+  }
+
+  public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+    method public androidx.room.RoomDatabase.Builder<T!> addAutoMigrationSpec(androidx.room.migration.AutoMigrationSpec);
+    method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
+    method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
+    method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
+    method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+    method public T build();
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> enableMultiInstanceInvalidation();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigration();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
+    method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
+    method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setAutoCloseTimeout(@IntRange(from=0) long, java.util.concurrent.TimeUnit);
+    method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+    method @androidx.room.ExperimentalRoomApi public androidx.room.RoomDatabase.Builder<T!> setMultiInstanceInvalidationServiceIntent(android.content.Intent);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setTransactionExecutor(java.util.concurrent.Executor);
+  }
+
+  public abstract static class RoomDatabase.Callback {
+    ctor public RoomDatabase.Callback();
+    method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onDestructiveMigration(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public enum RoomDatabase.JournalMode {
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
+    enum_constant @RequiresApi(android.os.Build.VERSION_CODES.JELLY_BEAN) public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+  }
+
+  public static class RoomDatabase.MigrationContainer {
+    ctor public RoomDatabase.MigrationContainer();
+    method public void addMigrations(androidx.room.migration.Migration!...);
+    method public void addMigrations(java.util.List<androidx.room.migration.Migration!>);
+    method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+    method public java.util.Map<java.lang.Integer!,java.util.Map<java.lang.Integer!,androidx.room.migration.Migration!>!> getMigrations();
+  }
+
+  public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+    ctor public RoomDatabase.PrepackagedDatabaseCallback();
+    method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public static interface RoomDatabase.QueryCallback {
+    method public void onQuery(String, java.util.List<java.lang.Object!>);
+  }
+
+}
+
+package androidx.room.migration {
+
+  public interface AutoMigrationSpec {
+    method public default void onPostMigrate(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public abstract class Migration {
+    ctor public Migration(int, int);
+    method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
+    field public final int endVersion;
+    field public final int startVersion;
+  }
+
+}
+
diff --git a/room/room-runtime/api/res-2.4.0-beta02.txt b/room/room-runtime/api/res-2.4.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/room-runtime/api/res-2.4.0-beta02.txt
diff --git a/room/room-runtime/api/restricted_2.4.0-beta02.txt b/room/room-runtime/api/restricted_2.4.0-beta02.txt
new file mode 100644
index 0000000..17a238e
--- /dev/null
+++ b/room/room-runtime/api/restricted_2.4.0-beta02.txt
@@ -0,0 +1,352 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class DatabaseConfiguration {
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, boolean, java.util.Set<java.lang.Integer!>?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?, java.util.List<androidx.room.migration.AutoMigrationSpec!>?);
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, android.content.Intent?, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?, java.util.List<androidx.room.migration.AutoMigrationSpec!>?);
+    method public boolean isMigrationRequired(int, int);
+    method @Deprecated public boolean isMigrationRequiredFrom(int);
+    field public final boolean allowDestructiveMigrationOnDowngrade;
+    field public final boolean allowMainThreadQueries;
+    field public final java.util.List<androidx.room.migration.AutoMigrationSpec!> autoMigrationSpecs;
+    field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
+    field public final android.content.Context context;
+    field public final String? copyFromAssetPath;
+    field public final java.io.File? copyFromFile;
+    field public final java.util.concurrent.Callable<java.io.InputStream!>? copyFromInputStream;
+    field public final androidx.room.RoomDatabase.JournalMode! journalMode;
+    field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
+    field public final boolean multiInstanceInvalidation;
+    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final android.content.Intent! multiInstanceInvalidationServiceIntent;
+    field public final String? name;
+    field public final androidx.room.RoomDatabase.PrepackagedDatabaseCallback? prepackagedDatabaseCallback;
+    field public final java.util.concurrent.Executor queryExecutor;
+    field public final boolean requireMigration;
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+    field public final java.util.concurrent.Executor transactionExecutor;
+    field public final java.util.List<java.lang.Object!> typeConverters;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityDeletionOrUpdateAdapter<T> extends androidx.room.SharedSQLiteStatement {
+    ctor public EntityDeletionOrUpdateAdapter(androidx.room.RoomDatabase!);
+    method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement!, T!);
+    method public final int handle(T!);
+    method public final int handleMultiple(Iterable<? extends T>!);
+    method public final int handleMultiple(T![]!);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityInsertionAdapter<T> extends androidx.room.SharedSQLiteStatement {
+    ctor public EntityInsertionAdapter(androidx.room.RoomDatabase!);
+    method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement!, T!);
+    method public final void insert(T!);
+    method public final void insert(T![]!);
+    method public final void insert(Iterable<? extends T>!);
+    method public final long insertAndReturnId(T!);
+    method public final long[]! insertAndReturnIdsArray(java.util.Collection<? extends T>!);
+    method public final long[]! insertAndReturnIdsArray(T![]!);
+    method public final Long![]! insertAndReturnIdsArrayBox(java.util.Collection<? extends T>!);
+    method public final Long![]! insertAndReturnIdsArrayBox(T![]!);
+    method public final java.util.List<java.lang.Long!>! insertAndReturnIdsList(T![]!);
+    method public final java.util.List<java.lang.Long!>! insertAndReturnIdsList(java.util.Collection<? extends T>!);
+  }
+
+  public class InvalidationTracker {
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.lang.String!...);
+    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.util.Map<java.lang.String!,java.lang.String!>!, java.util.Map<java.lang.String!,java.util.Set<java.lang.String!>!>!, java.lang.String!...);
+    method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addWeakObserver(androidx.room.InvalidationTracker.Observer!);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T!>! createLiveData(String![]!, java.util.concurrent.Callable<T!>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T!>! createLiveData(String![]!, boolean, java.util.concurrent.Callable<T!>!);
+    method public void refreshVersionsAsync();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void refreshVersionsSync();
+    method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer);
+  }
+
+  public abstract static class InvalidationTracker.Observer {
+    ctor protected InvalidationTracker.Observer(String, java.lang.String!...);
+    ctor public InvalidationTracker.Observer(String![]);
+    method public abstract void onInvalidated(java.util.Set<java.lang.String!>);
+  }
+
+  public class Room {
+    ctor @Deprecated public Room();
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> databaseBuilder(android.content.Context, Class<T!>, String);
+    method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T!> inMemoryDatabaseBuilder(android.content.Context, Class<T!>);
+    field public static final String MASTER_TABLE_NAME = "room_master_table";
+  }
+
+  public abstract class RoomDatabase {
+    ctor public RoomDatabase();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void assertNotMainThread();
+    method @Deprecated public void beginTransaction();
+    method @WorkerThread public abstract void clearAllTables();
+    method public void close();
+    method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String);
+    method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
+    method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration!);
+    method @Deprecated public void endTransaction();
+    method public androidx.room.InvalidationTracker getInvalidationTracker();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
+    method public java.util.concurrent.Executor getQueryExecutor();
+    method public java.util.concurrent.Executor getTransactionExecutor();
+    method public <T> T? getTypeConverter(Class<T!>);
+    method public boolean inTransaction();
+    method @CallSuper public void init(androidx.room.DatabaseConfiguration);
+    method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public boolean isOpen();
+    method public android.database.Cursor query(String, Object![]?);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery);
+    method public android.database.Cursor query(androidx.sqlite.db.SupportSQLiteQuery, android.os.CancellationSignal?);
+    method public void runInTransaction(Runnable);
+    method public <V> V! runInTransaction(java.util.concurrent.Callable<V!>);
+    method @Deprecated public void setTransactionSuccessful();
+    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MAX_BIND_PARAMETER_CNT = 999; // 0x3e7
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected java.util.List<androidx.room.RoomDatabase.Callback!>? mCallbacks;
+    field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
+  }
+
+  public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+    method public androidx.room.RoomDatabase.Builder<T!> addAutoMigrationSpec(androidx.room.migration.AutoMigrationSpec);
+    method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
+    method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
+    method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
+    method public androidx.room.RoomDatabase.Builder<T!> allowMainThreadQueries();
+    method public T build();
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromAsset(String, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromFile(java.io.File, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>);
+    method public androidx.room.RoomDatabase.Builder<T!> createFromInputStream(java.util.concurrent.Callable<java.io.InputStream!>, androidx.room.RoomDatabase.PrepackagedDatabaseCallback);
+    method public androidx.room.RoomDatabase.Builder<T!> enableMultiInstanceInvalidation();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigration();
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationFrom(int...);
+    method public androidx.room.RoomDatabase.Builder<T!> fallbackToDestructiveMigrationOnDowngrade();
+    method public androidx.room.RoomDatabase.Builder<T!> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
+    method public androidx.room.RoomDatabase.Builder<T!> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryCallback(androidx.room.RoomDatabase.QueryCallback, java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setQueryExecutor(java.util.concurrent.Executor);
+    method public androidx.room.RoomDatabase.Builder<T!> setTransactionExecutor(java.util.concurrent.Executor);
+  }
+
+  public abstract static class RoomDatabase.Callback {
+    ctor public RoomDatabase.Callback();
+    method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onDestructiveMigration(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public enum RoomDatabase.JournalMode {
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
+    enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
+    enum_constant @RequiresApi(android.os.Build.VERSION_CODES.JELLY_BEAN) public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+  }
+
+  public static class RoomDatabase.MigrationContainer {
+    ctor public RoomDatabase.MigrationContainer();
+    method public void addMigrations(androidx.room.migration.Migration!...);
+    method public void addMigrations(java.util.List<androidx.room.migration.Migration!>);
+    method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+    method public java.util.Map<java.lang.Integer!,java.util.Map<java.lang.Integer!,androidx.room.migration.Migration!>!> getMigrations();
+  }
+
+  public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
+    ctor public RoomDatabase.PrepackagedDatabaseCallback();
+    method public void onOpenPrepackagedDatabase(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public static interface RoomDatabase.QueryCallback {
+    method public void onQuery(String, java.util.List<java.lang.Object!>);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomOpenHelper extends androidx.sqlite.db.SupportSQLiteOpenHelper.Callback {
+    ctor public RoomOpenHelper(androidx.room.DatabaseConfiguration, androidx.room.RoomOpenHelper.Delegate, String, String);
+    ctor public RoomOpenHelper(androidx.room.DatabaseConfiguration, androidx.room.RoomOpenHelper.Delegate, String);
+    method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method public void onUpgrade(androidx.sqlite.db.SupportSQLiteDatabase!, int, int);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract static class RoomOpenHelper.Delegate {
+    ctor public RoomOpenHelper.Delegate(int);
+    method protected abstract void createAllTables(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected abstract void dropAllTables(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected abstract void onCreate(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected abstract void onOpen(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected void onPostMigrate(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected void onPreMigrate(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method protected androidx.room.RoomOpenHelper.ValidationResult onValidateSchema(androidx.sqlite.db.SupportSQLiteDatabase);
+    method @Deprecated protected void validateMigration(androidx.sqlite.db.SupportSQLiteDatabase!);
+    field public final int version;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class RoomOpenHelper.ValidationResult {
+    ctor public RoomOpenHelper.ValidationResult(boolean, String?);
+    field public final String? expectedFoundMsg;
+    field public final boolean isValid;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomSQLiteQuery implements androidx.sqlite.db.SupportSQLiteProgram androidx.sqlite.db.SupportSQLiteQuery {
+    method public static androidx.room.RoomSQLiteQuery! acquire(String!, int);
+    method public void bindBlob(int, byte[]!);
+    method public void bindDouble(int, double);
+    method public void bindLong(int, long);
+    method public void bindNull(int);
+    method public void bindString(int, String!);
+    method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
+    method public void clearBindings();
+    method public void close();
+    method public void copyArgumentsFrom(androidx.room.RoomSQLiteQuery!);
+    method public static androidx.room.RoomSQLiteQuery! copyFrom(androidx.sqlite.db.SupportSQLiteQuery!);
+    method public int getArgCount();
+    method public String! getSql();
+    method public void release();
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class SharedSQLiteStatement {
+    ctor public SharedSQLiteStatement(androidx.room.RoomDatabase!);
+    method public androidx.sqlite.db.SupportSQLiteStatement! acquire();
+    method protected void assertNotMainThread();
+    method protected abstract String! createQuery();
+    method public void release(androidx.sqlite.db.SupportSQLiteStatement!);
+  }
+
+}
+
+package androidx.room.migration {
+
+  public interface AutoMigrationSpec {
+    method public default void onPostMigrate(androidx.sqlite.db.SupportSQLiteDatabase);
+  }
+
+  public abstract class Migration {
+    ctor public Migration(int, int);
+    method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
+    field public final int endVersion;
+    field public final int startVersion;
+  }
+
+}
+
+package androidx.room.paging {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class LimitOffsetDataSource<T> extends androidx.paging.PositionalDataSource<T> {
+    ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, java.lang.String!...);
+    ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, boolean, java.lang.String!...);
+    ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase, androidx.room.RoomSQLiteQuery, boolean, java.lang.String!...);
+    ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase, androidx.room.RoomSQLiteQuery, boolean, boolean, java.lang.String!...);
+    method protected abstract java.util.List<T!> convertRows(android.database.Cursor);
+    method public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
+    method public void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
+  }
+
+}
+
+package androidx.room.util {
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CopyLock {
+    ctor public CopyLock(String, java.io.File, boolean);
+    method public void lock();
+    method public void unlock();
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CursorUtil {
+    method public static android.database.Cursor copyAndClose(android.database.Cursor);
+    method public static int getColumnIndex(android.database.Cursor, String);
+    method public static int getColumnIndexOrThrow(android.database.Cursor, String);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DBUtil {
+    method public static android.os.CancellationSignal? createCancellationSignal();
+    method public static void dropFtsSyncTriggers(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method public static void foreignKeyCheck(androidx.sqlite.db.SupportSQLiteDatabase, String);
+    method @Deprecated public static android.database.Cursor query(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean);
+    method public static android.database.Cursor query(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, android.os.CancellationSignal?);
+    method public static int readVersion(java.io.File) throws java.io.IOException;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FileUtil {
+    method public static void copy(java.nio.channels.ReadableByteChannel, java.nio.channels.FileChannel) throws java.io.IOException;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class FtsTableInfo {
+    ctor public FtsTableInfo(String!, java.util.Set<java.lang.String!>!, java.util.Set<java.lang.String!>!);
+    ctor public FtsTableInfo(String!, java.util.Set<java.lang.String!>!, String!);
+    method public static androidx.room.util.FtsTableInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+    field public final java.util.Set<java.lang.String!>! columns;
+    field public final String! name;
+    field public final java.util.Set<java.lang.String!>! options;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class StringUtil {
+    method public static void appendPlaceholders(StringBuilder!, int);
+    method public static String? joinIntoString(java.util.List<java.lang.Integer!>?);
+    method public static StringBuilder! newStringBuilder();
+    method public static java.util.List<java.lang.Integer!>? splitToIntList(String?);
+    field public static final String![]! EMPTY_STRING_ARRAY;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TableInfo {
+    ctor public TableInfo(String!, java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>!, java.util.Set<androidx.room.util.TableInfo.ForeignKey!>!, java.util.Set<androidx.room.util.TableInfo.Index!>!);
+    ctor public TableInfo(String!, java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>!, java.util.Set<androidx.room.util.TableInfo.ForeignKey!>!);
+    method public static androidx.room.util.TableInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+    field public static final int CREATED_FROM_DATABASE = 2; // 0x2
+    field public static final int CREATED_FROM_ENTITY = 1; // 0x1
+    field public static final int CREATED_FROM_UNKNOWN = 0; // 0x0
+    field public final java.util.Map<java.lang.String!,androidx.room.util.TableInfo.Column!>! columns;
+    field public final java.util.Set<androidx.room.util.TableInfo.ForeignKey!>! foreignKeys;
+    field public final java.util.Set<androidx.room.util.TableInfo.Index!>? indices;
+    field public final String! name;
+  }
+
+  public static final class TableInfo.Column {
+    ctor @Deprecated public TableInfo.Column(String!, String!, boolean, int);
+    ctor public TableInfo.Column(String!, String!, boolean, int, String!, int);
+    method public static boolean defaultValueEquals(String, String?);
+    method public boolean isPrimaryKey();
+    field @androidx.room.ColumnInfo.SQLiteTypeAffinity public final int affinity;
+    field public final String! defaultValue;
+    field public final String! name;
+    field public final boolean notNull;
+    field public final int primaryKeyPosition;
+    field public final String! type;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class TableInfo.ForeignKey {
+    ctor public TableInfo.ForeignKey(String, String, String, java.util.List<java.lang.String!>, java.util.List<java.lang.String!>);
+    field public final java.util.List<java.lang.String!> columnNames;
+    field public final String onDelete;
+    field public final String onUpdate;
+    field public final java.util.List<java.lang.String!> referenceColumnNames;
+    field public final String referenceTable;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final class TableInfo.Index {
+    ctor @Deprecated public TableInfo.Index(String!, boolean, java.util.List<java.lang.String!>!);
+    ctor public TableInfo.Index(String!, boolean, java.util.List<java.lang.String!>!, java.util.List<java.lang.String!>!);
+    field public static final String DEFAULT_PREFIX = "index_";
+    field public final java.util.List<java.lang.String!>! columns;
+    field public final String! name;
+    field public final java.util.List<java.lang.String!>! orders;
+    field public final boolean unique;
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class UUIDUtil {
+    method public static java.util.UUID convertByteToUUID(byte[]);
+    method public static byte[] convertUUIDToByte(java.util.UUID);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ViewInfo {
+    ctor public ViewInfo(String!, String!);
+    method public static androidx.room.util.ViewInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+    field public final String! name;
+    field public final String! sql;
+  }
+
+}
+
diff --git a/room/room-rxjava2/api/2.4.0-beta02.txt b/room/room-rxjava2/api/2.4.0-beta02.txt
new file mode 100644
index 0000000..64b6fe4
--- /dev/null
+++ b/room/room-rxjava2/api/2.4.0-beta02.txt
@@ -0,0 +1,16 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String!);
+  }
+
+  public class RxRoom {
+    ctor @Deprecated public RxRoom();
+    method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+    method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+    field public static final Object! NOTHING;
+  }
+
+}
+
diff --git a/room/room-rxjava2/api/public_plus_experimental_2.4.0-beta02.txt b/room/room-rxjava2/api/public_plus_experimental_2.4.0-beta02.txt
new file mode 100644
index 0000000..64b6fe4
--- /dev/null
+++ b/room/room-rxjava2/api/public_plus_experimental_2.4.0-beta02.txt
@@ -0,0 +1,16 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String!);
+  }
+
+  public class RxRoom {
+    ctor @Deprecated public RxRoom();
+    method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+    method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+    field public static final Object! NOTHING;
+  }
+
+}
+
diff --git a/room/room-rxjava2/api/res-2.4.0-beta02.txt b/room/room-rxjava2/api/res-2.4.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/room-rxjava2/api/res-2.4.0-beta02.txt
diff --git a/room/room-rxjava2/api/restricted_2.4.0-beta02.txt b/room/room-rxjava2/api/restricted_2.4.0-beta02.txt
new file mode 100644
index 0000000..5505f93
--- /dev/null
+++ b/room/room-rxjava2/api/restricted_2.4.0-beta02.txt
@@ -0,0 +1,21 @@
+// Signature format: 4.0
+package androidx.room {
+
+  public class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String!);
+  }
+
+  public class RxRoom {
+    ctor @Deprecated public RxRoom();
+    method public static io.reactivex.Flowable<java.lang.Object!>! createFlowable(androidx.room.RoomDatabase!, java.lang.String!...);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T!>! createFlowable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
+    method public static io.reactivex.Observable<java.lang.Object!>! createObservable(androidx.room.RoomDatabase!, java.lang.String!...);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, String![]!, java.util.concurrent.Callable<T!>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T!>! createObservable(androidx.room.RoomDatabase!, boolean, String![]!, java.util.concurrent.Callable<T!>!);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Single<T!>! createSingle(java.util.concurrent.Callable<T!>!);
+    field public static final Object! NOTHING;
+  }
+
+}
+
diff --git a/room/room-rxjava3/api/2.4.0-beta02.txt b/room/room-rxjava3/api/2.4.0-beta02.txt
new file mode 100644
index 0000000..6b78281
--- /dev/null
+++ b/room/room-rxjava3/api/2.4.0-beta02.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.room.rxjava3 {
+
+  public final class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String);
+  }
+
+  public final class RxRoom {
+    method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
+    method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+    field public static final Object NOTHING;
+  }
+
+}
+
diff --git a/room/room-rxjava3/api/public_plus_experimental_2.4.0-beta02.txt b/room/room-rxjava3/api/public_plus_experimental_2.4.0-beta02.txt
new file mode 100644
index 0000000..6b78281
--- /dev/null
+++ b/room/room-rxjava3/api/public_plus_experimental_2.4.0-beta02.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.room.rxjava3 {
+
+  public final class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String);
+  }
+
+  public final class RxRoom {
+    method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
+    method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+    field public static final Object NOTHING;
+  }
+
+}
+
diff --git a/room/room-rxjava3/api/res-2.4.0-beta02.txt b/room/room-rxjava3/api/res-2.4.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/room-rxjava3/api/res-2.4.0-beta02.txt
diff --git a/room/room-rxjava3/api/restricted_2.4.0-beta02.txt b/room/room-rxjava3/api/restricted_2.4.0-beta02.txt
new file mode 100644
index 0000000..0680710
--- /dev/null
+++ b/room/room-rxjava3/api/restricted_2.4.0-beta02.txt
@@ -0,0 +1,18 @@
+// Signature format: 4.0
+package androidx.room.rxjava3 {
+
+  public final class EmptyResultSetException extends java.lang.RuntimeException {
+    ctor public EmptyResultSetException(String);
+  }
+
+  public final class RxRoom {
+    method public static io.reactivex.rxjava3.core.Flowable<java.lang.Object!> createFlowable(androidx.room.RoomDatabase, java.lang.String!...);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Flowable<T!> createFlowable(androidx.room.RoomDatabase, boolean, String![], java.util.concurrent.Callable<T!>);
+    method public static io.reactivex.rxjava3.core.Observable<java.lang.Object!> createObservable(androidx.room.RoomDatabase, java.lang.String!...);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Observable<T!> createObservable(androidx.room.RoomDatabase, boolean, String![], java.util.concurrent.Callable<T!>);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.rxjava3.core.Single<T!> createSingle(java.util.concurrent.Callable<T!>);
+    field public static final Object NOTHING;
+  }
+
+}
+
diff --git a/room/room-testing/api/2.4.0-beta02.txt b/room/room-testing/api/2.4.0-beta02.txt
new file mode 100644
index 0000000..ea0639e
--- /dev/null
+++ b/room/room-testing/api/2.4.0-beta02.txt
@@ -0,0 +1,17 @@
+// Signature format: 4.0
+package androidx.room.testing {
+
+  public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!);
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>);
+    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>);
+    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory);
+    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method public void closeWhenFinished(androidx.room.RoomDatabase!);
+    method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+    method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+  }
+
+}
+
diff --git a/room/room-testing/api/public_plus_experimental_2.4.0-beta02.txt b/room/room-testing/api/public_plus_experimental_2.4.0-beta02.txt
new file mode 100644
index 0000000..ea0639e
--- /dev/null
+++ b/room/room-testing/api/public_plus_experimental_2.4.0-beta02.txt
@@ -0,0 +1,17 @@
+// Signature format: 4.0
+package androidx.room.testing {
+
+  public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!);
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>);
+    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>);
+    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory);
+    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method public void closeWhenFinished(androidx.room.RoomDatabase!);
+    method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+    method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+  }
+
+}
+
diff --git a/room/room-testing/api/res-2.4.0-beta02.txt b/room/room-testing/api/res-2.4.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/room-testing/api/res-2.4.0-beta02.txt
diff --git a/room/room-testing/api/restricted_2.4.0-beta02.txt b/room/room-testing/api/restricted_2.4.0-beta02.txt
new file mode 100644
index 0000000..ea0639e
--- /dev/null
+++ b/room/room-testing/api/restricted_2.4.0-beta02.txt
@@ -0,0 +1,17 @@
+// Signature format: 4.0
+package androidx.room.testing {
+
+  public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!);
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>);
+    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>);
+    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory);
+    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+    method public void closeWhenFinished(androidx.room.RoomDatabase!);
+    method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+    method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+  }
+
+}
+
diff --git a/sqlite/sqlite-framework/api/2.2.0-beta02.txt b/sqlite/sqlite-framework/api/2.2.0-beta02.txt
new file mode 100644
index 0000000..526c565
--- /dev/null
+++ b/sqlite/sqlite-framework/api/2.2.0-beta02.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.sqlite.db.framework {
+
+  public final class FrameworkSQLiteOpenHelperFactory implements androidx.sqlite.db.SupportSQLiteOpenHelper.Factory {
+    ctor public FrameworkSQLiteOpenHelperFactory();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper create(androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration);
+  }
+
+}
+
diff --git a/sqlite/sqlite-framework/api/public_plus_experimental_2.2.0-beta02.txt b/sqlite/sqlite-framework/api/public_plus_experimental_2.2.0-beta02.txt
new file mode 100644
index 0000000..526c565
--- /dev/null
+++ b/sqlite/sqlite-framework/api/public_plus_experimental_2.2.0-beta02.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.sqlite.db.framework {
+
+  public final class FrameworkSQLiteOpenHelperFactory implements androidx.sqlite.db.SupportSQLiteOpenHelper.Factory {
+    ctor public FrameworkSQLiteOpenHelperFactory();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper create(androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration);
+  }
+
+}
+
diff --git a/sqlite/sqlite-framework/api/res-2.2.0-beta02.txt b/sqlite/sqlite-framework/api/res-2.2.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sqlite/sqlite-framework/api/res-2.2.0-beta02.txt
diff --git a/sqlite/sqlite-framework/api/restricted_2.2.0-beta02.txt b/sqlite/sqlite-framework/api/restricted_2.2.0-beta02.txt
new file mode 100644
index 0000000..526c565
--- /dev/null
+++ b/sqlite/sqlite-framework/api/restricted_2.2.0-beta02.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.sqlite.db.framework {
+
+  public final class FrameworkSQLiteOpenHelperFactory implements androidx.sqlite.db.SupportSQLiteOpenHelper.Factory {
+    ctor public FrameworkSQLiteOpenHelperFactory();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper create(androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration);
+  }
+
+}
+
diff --git a/sqlite/sqlite-ktx/api/2.2.0-beta02.txt b/sqlite/sqlite-ktx/api/2.2.0-beta02.txt
new file mode 100644
index 0000000..26086b4
--- /dev/null
+++ b/sqlite/sqlite-ktx/api/2.2.0-beta02.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.sqlite.db {
+
+  public final class SupportSQLiteDatabaseKt {
+    method public static inline <T> T! transaction(androidx.sqlite.db.SupportSQLiteDatabase, optional boolean exclusive, kotlin.jvm.functions.Function1<? super androidx.sqlite.db.SupportSQLiteDatabase,? extends T> body);
+  }
+
+}
+
diff --git a/sqlite/sqlite-ktx/api/public_plus_experimental_2.2.0-beta02.txt b/sqlite/sqlite-ktx/api/public_plus_experimental_2.2.0-beta02.txt
new file mode 100644
index 0000000..26086b4
--- /dev/null
+++ b/sqlite/sqlite-ktx/api/public_plus_experimental_2.2.0-beta02.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.sqlite.db {
+
+  public final class SupportSQLiteDatabaseKt {
+    method public static inline <T> T! transaction(androidx.sqlite.db.SupportSQLiteDatabase, optional boolean exclusive, kotlin.jvm.functions.Function1<? super androidx.sqlite.db.SupportSQLiteDatabase,? extends T> body);
+  }
+
+}
+
diff --git a/sqlite/sqlite-ktx/api/res-2.2.0-beta02.txt b/sqlite/sqlite-ktx/api/res-2.2.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sqlite/sqlite-ktx/api/res-2.2.0-beta02.txt
diff --git a/sqlite/sqlite-ktx/api/restricted_2.2.0-beta02.txt b/sqlite/sqlite-ktx/api/restricted_2.2.0-beta02.txt
new file mode 100644
index 0000000..26086b4
--- /dev/null
+++ b/sqlite/sqlite-ktx/api/restricted_2.2.0-beta02.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.sqlite.db {
+
+  public final class SupportSQLiteDatabaseKt {
+    method public static inline <T> T! transaction(androidx.sqlite.db.SupportSQLiteDatabase, optional boolean exclusive, kotlin.jvm.functions.Function1<? super androidx.sqlite.db.SupportSQLiteDatabase,? extends T> body);
+  }
+
+}
+
diff --git a/sqlite/sqlite/api/2.2.0-beta02.txt b/sqlite/sqlite/api/2.2.0-beta02.txt
new file mode 100644
index 0000000..dbdea61
--- /dev/null
+++ b/sqlite/sqlite/api/2.2.0-beta02.txt
@@ -0,0 +1,130 @@
+// Signature format: 4.0
+package androidx.sqlite.db {
+
+  public final class SimpleSQLiteQuery implements androidx.sqlite.db.SupportSQLiteQuery {
+    ctor public SimpleSQLiteQuery(String!, Object![]?);
+    ctor public SimpleSQLiteQuery(String!);
+    method public static void bind(androidx.sqlite.db.SupportSQLiteProgram!, Object![]!);
+    method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
+    method public int getArgCount();
+    method public String! getSql();
+  }
+
+  public interface SupportSQLiteDatabase extends java.io.Closeable {
+    method public void beginTransaction();
+    method public void beginTransactionNonExclusive();
+    method public void beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener!);
+    method public void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener!);
+    method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String!);
+    method public int delete(String!, String!, Object![]!);
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public void disableWriteAheadLogging();
+    method public boolean enableWriteAheadLogging();
+    method public void endTransaction();
+    method public default void execPerConnectionSQL(String, Object![]?);
+    method public void execSQL(String!) throws android.database.SQLException;
+    method public void execSQL(String!, Object![]!) throws android.database.SQLException;
+    method public java.util.List<android.util.Pair<java.lang.String!,java.lang.String!>!>! getAttachedDbs();
+    method public long getMaximumSize();
+    method public long getPageSize();
+    method public String! getPath();
+    method public int getVersion();
+    method public boolean inTransaction();
+    method public long insert(String!, int, android.content.ContentValues!) throws android.database.SQLException;
+    method public boolean isDatabaseIntegrityOk();
+    method public boolean isDbLockedByCurrentThread();
+    method public default boolean isExecPerConnectionSQLSupported();
+    method public boolean isOpen();
+    method public boolean isReadOnly();
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public boolean isWriteAheadLoggingEnabled();
+    method public boolean needUpgrade(int);
+    method public android.database.Cursor! query(String!);
+    method public android.database.Cursor! query(String!, Object![]!);
+    method public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!);
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!, android.os.CancellationSignal!);
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public void setForeignKeyConstraintsEnabled(boolean);
+    method public void setLocale(java.util.Locale!);
+    method public void setMaxSqlCacheSize(int);
+    method public long setMaximumSize(long);
+    method public void setPageSize(long);
+    method public void setTransactionSuccessful();
+    method public void setVersion(int);
+    method public int update(String!, int, android.content.ContentValues!, String!, Object![]!);
+    method public boolean yieldIfContendedSafely();
+    method public boolean yieldIfContendedSafely(long);
+  }
+
+  public interface SupportSQLiteOpenHelper extends java.io.Closeable {
+    method public void close();
+    method public String? getDatabaseName();
+    method public androidx.sqlite.db.SupportSQLiteDatabase! getReadableDatabase();
+    method public androidx.sqlite.db.SupportSQLiteDatabase! getWritableDatabase();
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public void setWriteAheadLoggingEnabled(boolean);
+  }
+
+  public abstract static class SupportSQLiteOpenHelper.Callback {
+    ctor public SupportSQLiteOpenHelper.Callback(int);
+    method public void onConfigure(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onCorruption(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public abstract void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onDowngrade(androidx.sqlite.db.SupportSQLiteDatabase, int, int);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public abstract void onUpgrade(androidx.sqlite.db.SupportSQLiteDatabase, int, int);
+    field public final int version;
+  }
+
+  public static class SupportSQLiteOpenHelper.Configuration {
+    method public static androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder builder(android.content.Context);
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
+    field public final android.content.Context context;
+    field public final String? name;
+    field public final boolean useNoBackupDirectory;
+  }
+
+  public static class SupportSQLiteOpenHelper.Configuration.Builder {
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder noBackupDirectory(boolean);
+  }
+
+  public static interface SupportSQLiteOpenHelper.Factory {
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper create(androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration);
+  }
+
+  public interface SupportSQLiteProgram extends java.io.Closeable {
+    method public void bindBlob(int, byte[]!);
+    method public void bindDouble(int, double);
+    method public void bindLong(int, long);
+    method public void bindNull(int);
+    method public void bindString(int, String!);
+    method public void clearBindings();
+  }
+
+  public interface SupportSQLiteQuery {
+    method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
+    method public int getArgCount();
+    method public String! getSql();
+  }
+
+  public final class SupportSQLiteQueryBuilder {
+    method public static androidx.sqlite.db.SupportSQLiteQueryBuilder! builder(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! columns(String![]!);
+    method public androidx.sqlite.db.SupportSQLiteQuery! create();
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! distinct();
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! groupBy(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! having(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! limit(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! orderBy(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! selection(String!, Object![]!);
+  }
+
+  public interface SupportSQLiteStatement extends androidx.sqlite.db.SupportSQLiteProgram {
+    method public void execute();
+    method public long executeInsert();
+    method public int executeUpdateDelete();
+    method public long simpleQueryForLong();
+    method public String! simpleQueryForString();
+  }
+
+}
+
diff --git a/sqlite/sqlite/api/public_plus_experimental_2.2.0-beta02.txt b/sqlite/sqlite/api/public_plus_experimental_2.2.0-beta02.txt
new file mode 100644
index 0000000..dbdea61
--- /dev/null
+++ b/sqlite/sqlite/api/public_plus_experimental_2.2.0-beta02.txt
@@ -0,0 +1,130 @@
+// Signature format: 4.0
+package androidx.sqlite.db {
+
+  public final class SimpleSQLiteQuery implements androidx.sqlite.db.SupportSQLiteQuery {
+    ctor public SimpleSQLiteQuery(String!, Object![]?);
+    ctor public SimpleSQLiteQuery(String!);
+    method public static void bind(androidx.sqlite.db.SupportSQLiteProgram!, Object![]!);
+    method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
+    method public int getArgCount();
+    method public String! getSql();
+  }
+
+  public interface SupportSQLiteDatabase extends java.io.Closeable {
+    method public void beginTransaction();
+    method public void beginTransactionNonExclusive();
+    method public void beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener!);
+    method public void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener!);
+    method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String!);
+    method public int delete(String!, String!, Object![]!);
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public void disableWriteAheadLogging();
+    method public boolean enableWriteAheadLogging();
+    method public void endTransaction();
+    method public default void execPerConnectionSQL(String, Object![]?);
+    method public void execSQL(String!) throws android.database.SQLException;
+    method public void execSQL(String!, Object![]!) throws android.database.SQLException;
+    method public java.util.List<android.util.Pair<java.lang.String!,java.lang.String!>!>! getAttachedDbs();
+    method public long getMaximumSize();
+    method public long getPageSize();
+    method public String! getPath();
+    method public int getVersion();
+    method public boolean inTransaction();
+    method public long insert(String!, int, android.content.ContentValues!) throws android.database.SQLException;
+    method public boolean isDatabaseIntegrityOk();
+    method public boolean isDbLockedByCurrentThread();
+    method public default boolean isExecPerConnectionSQLSupported();
+    method public boolean isOpen();
+    method public boolean isReadOnly();
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public boolean isWriteAheadLoggingEnabled();
+    method public boolean needUpgrade(int);
+    method public android.database.Cursor! query(String!);
+    method public android.database.Cursor! query(String!, Object![]!);
+    method public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!);
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!, android.os.CancellationSignal!);
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public void setForeignKeyConstraintsEnabled(boolean);
+    method public void setLocale(java.util.Locale!);
+    method public void setMaxSqlCacheSize(int);
+    method public long setMaximumSize(long);
+    method public void setPageSize(long);
+    method public void setTransactionSuccessful();
+    method public void setVersion(int);
+    method public int update(String!, int, android.content.ContentValues!, String!, Object![]!);
+    method public boolean yieldIfContendedSafely();
+    method public boolean yieldIfContendedSafely(long);
+  }
+
+  public interface SupportSQLiteOpenHelper extends java.io.Closeable {
+    method public void close();
+    method public String? getDatabaseName();
+    method public androidx.sqlite.db.SupportSQLiteDatabase! getReadableDatabase();
+    method public androidx.sqlite.db.SupportSQLiteDatabase! getWritableDatabase();
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public void setWriteAheadLoggingEnabled(boolean);
+  }
+
+  public abstract static class SupportSQLiteOpenHelper.Callback {
+    ctor public SupportSQLiteOpenHelper.Callback(int);
+    method public void onConfigure(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onCorruption(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public abstract void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onDowngrade(androidx.sqlite.db.SupportSQLiteDatabase, int, int);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public abstract void onUpgrade(androidx.sqlite.db.SupportSQLiteDatabase, int, int);
+    field public final int version;
+  }
+
+  public static class SupportSQLiteOpenHelper.Configuration {
+    method public static androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder builder(android.content.Context);
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
+    field public final android.content.Context context;
+    field public final String? name;
+    field public final boolean useNoBackupDirectory;
+  }
+
+  public static class SupportSQLiteOpenHelper.Configuration.Builder {
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder noBackupDirectory(boolean);
+  }
+
+  public static interface SupportSQLiteOpenHelper.Factory {
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper create(androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration);
+  }
+
+  public interface SupportSQLiteProgram extends java.io.Closeable {
+    method public void bindBlob(int, byte[]!);
+    method public void bindDouble(int, double);
+    method public void bindLong(int, long);
+    method public void bindNull(int);
+    method public void bindString(int, String!);
+    method public void clearBindings();
+  }
+
+  public interface SupportSQLiteQuery {
+    method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
+    method public int getArgCount();
+    method public String! getSql();
+  }
+
+  public final class SupportSQLiteQueryBuilder {
+    method public static androidx.sqlite.db.SupportSQLiteQueryBuilder! builder(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! columns(String![]!);
+    method public androidx.sqlite.db.SupportSQLiteQuery! create();
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! distinct();
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! groupBy(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! having(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! limit(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! orderBy(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! selection(String!, Object![]!);
+  }
+
+  public interface SupportSQLiteStatement extends androidx.sqlite.db.SupportSQLiteProgram {
+    method public void execute();
+    method public long executeInsert();
+    method public int executeUpdateDelete();
+    method public long simpleQueryForLong();
+    method public String! simpleQueryForString();
+  }
+
+}
+
diff --git a/sqlite/sqlite/api/res-2.2.0-beta02.txt b/sqlite/sqlite/api/res-2.2.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sqlite/sqlite/api/res-2.2.0-beta02.txt
diff --git a/sqlite/sqlite/api/restricted_2.2.0-beta02.txt b/sqlite/sqlite/api/restricted_2.2.0-beta02.txt
new file mode 100644
index 0000000..dbdea61
--- /dev/null
+++ b/sqlite/sqlite/api/restricted_2.2.0-beta02.txt
@@ -0,0 +1,130 @@
+// Signature format: 4.0
+package androidx.sqlite.db {
+
+  public final class SimpleSQLiteQuery implements androidx.sqlite.db.SupportSQLiteQuery {
+    ctor public SimpleSQLiteQuery(String!, Object![]?);
+    ctor public SimpleSQLiteQuery(String!);
+    method public static void bind(androidx.sqlite.db.SupportSQLiteProgram!, Object![]!);
+    method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
+    method public int getArgCount();
+    method public String! getSql();
+  }
+
+  public interface SupportSQLiteDatabase extends java.io.Closeable {
+    method public void beginTransaction();
+    method public void beginTransactionNonExclusive();
+    method public void beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener!);
+    method public void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener!);
+    method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String!);
+    method public int delete(String!, String!, Object![]!);
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public void disableWriteAheadLogging();
+    method public boolean enableWriteAheadLogging();
+    method public void endTransaction();
+    method public default void execPerConnectionSQL(String, Object![]?);
+    method public void execSQL(String!) throws android.database.SQLException;
+    method public void execSQL(String!, Object![]!) throws android.database.SQLException;
+    method public java.util.List<android.util.Pair<java.lang.String!,java.lang.String!>!>! getAttachedDbs();
+    method public long getMaximumSize();
+    method public long getPageSize();
+    method public String! getPath();
+    method public int getVersion();
+    method public boolean inTransaction();
+    method public long insert(String!, int, android.content.ContentValues!) throws android.database.SQLException;
+    method public boolean isDatabaseIntegrityOk();
+    method public boolean isDbLockedByCurrentThread();
+    method public default boolean isExecPerConnectionSQLSupported();
+    method public boolean isOpen();
+    method public boolean isReadOnly();
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public boolean isWriteAheadLoggingEnabled();
+    method public boolean needUpgrade(int);
+    method public android.database.Cursor! query(String!);
+    method public android.database.Cursor! query(String!, Object![]!);
+    method public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!);
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!, android.os.CancellationSignal!);
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public void setForeignKeyConstraintsEnabled(boolean);
+    method public void setLocale(java.util.Locale!);
+    method public void setMaxSqlCacheSize(int);
+    method public long setMaximumSize(long);
+    method public void setPageSize(long);
+    method public void setTransactionSuccessful();
+    method public void setVersion(int);
+    method public int update(String!, int, android.content.ContentValues!, String!, Object![]!);
+    method public boolean yieldIfContendedSafely();
+    method public boolean yieldIfContendedSafely(long);
+  }
+
+  public interface SupportSQLiteOpenHelper extends java.io.Closeable {
+    method public void close();
+    method public String? getDatabaseName();
+    method public androidx.sqlite.db.SupportSQLiteDatabase! getReadableDatabase();
+    method public androidx.sqlite.db.SupportSQLiteDatabase! getWritableDatabase();
+    method @RequiresApi(api=android.os.Build.VERSION_CODES.JELLY_BEAN) public void setWriteAheadLoggingEnabled(boolean);
+  }
+
+  public abstract static class SupportSQLiteOpenHelper.Callback {
+    ctor public SupportSQLiteOpenHelper.Callback(int);
+    method public void onConfigure(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onCorruption(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public abstract void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public void onDowngrade(androidx.sqlite.db.SupportSQLiteDatabase, int, int);
+    method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+    method public abstract void onUpgrade(androidx.sqlite.db.SupportSQLiteDatabase, int, int);
+    field public final int version;
+  }
+
+  public static class SupportSQLiteOpenHelper.Configuration {
+    method public static androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder builder(android.content.Context);
+    field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Callback callback;
+    field public final android.content.Context context;
+    field public final String? name;
+    field public final boolean useNoBackupDirectory;
+  }
+
+  public static class SupportSQLiteOpenHelper.Configuration.Builder {
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration build();
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder callback(androidx.sqlite.db.SupportSQLiteOpenHelper.Callback);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder name(String?);
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration.Builder noBackupDirectory(boolean);
+  }
+
+  public static interface SupportSQLiteOpenHelper.Factory {
+    method public androidx.sqlite.db.SupportSQLiteOpenHelper create(androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration);
+  }
+
+  public interface SupportSQLiteProgram extends java.io.Closeable {
+    method public void bindBlob(int, byte[]!);
+    method public void bindDouble(int, double);
+    method public void bindLong(int, long);
+    method public void bindNull(int);
+    method public void bindString(int, String!);
+    method public void clearBindings();
+  }
+
+  public interface SupportSQLiteQuery {
+    method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
+    method public int getArgCount();
+    method public String! getSql();
+  }
+
+  public final class SupportSQLiteQueryBuilder {
+    method public static androidx.sqlite.db.SupportSQLiteQueryBuilder! builder(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! columns(String![]!);
+    method public androidx.sqlite.db.SupportSQLiteQuery! create();
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! distinct();
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! groupBy(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! having(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! limit(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! orderBy(String!);
+    method public androidx.sqlite.db.SupportSQLiteQueryBuilder! selection(String!, Object![]!);
+  }
+
+  public interface SupportSQLiteStatement extends androidx.sqlite.db.SupportSQLiteProgram {
+    method public void execute();
+    method public long executeInsert();
+    method public int executeUpdateDelete();
+    method public long simpleQueryForLong();
+    method public String! simpleQueryForString();
+  }
+
+}
+
diff --git a/startup/startup-runtime/src/androidTest/java/androidx/startup/EagerlyInitializedTest.kt b/startup/startup-runtime/src/androidTest/java/androidx/startup/EagerlyInitializedTest.kt
new file mode 100644
index 0000000..76e666f
--- /dev/null
+++ b/startup/startup-runtime/src/androidTest/java/androidx/startup/EagerlyInitializedTest.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.startup
+
+import android.content.Context
+import android.os.Bundle
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class EagerlyInitializedTest {
+    class A : Initializer<Unit> {
+        override fun create(context: Context) {
+            val initializer = AppInitializer.getInstance(context)
+            assertTrue(initializer.isEagerlyInitialized(javaClass))
+        }
+
+        override fun dependencies() = listOf(B::class.java)
+    }
+
+    class B : Initializer<Unit> {
+        override fun create(context: Context) {
+            val initializer = AppInitializer.getInstance(context)
+            assertTrue(initializer.isEagerlyInitialized(javaClass))
+        }
+
+        override fun dependencies(): List<Class<out Initializer<*>>> = listOf()
+    }
+
+    private lateinit var context: Context
+    private lateinit var appInitializer: AppInitializer
+
+    @Before
+    fun setUp() {
+        context = ApplicationProvider.getApplicationContext()
+        appInitializer = AppInitializer(context)
+        AppInitializer.setDelegate(appInitializer)
+    }
+
+    @Test
+    fun testEagerlyInitialize() {
+        val metadata = Bundle()
+        metadata.putString(A::class.java.name, STARTUP)
+        metadata.putString(B::class.java.name, STARTUP)
+        appInitializer.discoverAndInitialize(metadata)
+    }
+
+    companion object {
+        const val STARTUP = "androidx.startup"
+    }
+}
diff --git a/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java b/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
index 1d23647..a56b93e 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.tracing.Trace;
 
 import java.util.HashMap;
@@ -92,6 +93,17 @@
     }
 
     /**
+     * Sets an {@link AppInitializer} delegate. Useful in the context of testing.
+     *
+     * @param delegate The instance of {@link AppInitializer} to be used as a delegate.
+     */
+    static void setDelegate(@NonNull AppInitializer delegate) {
+        synchronized (sLock) {
+            sInstance = delegate;
+        }
+    }
+
+    /**
      * Initializes a {@link Initializer} class type.
      *
      * @param component The {@link Class} of {@link Initializer} to initialize.
@@ -171,7 +183,6 @@
         }
     }
 
-    @SuppressWarnings("unchecked")
     void discoverAndInitialize() {
         try {
             Trace.beginSection(SECTION_NAME);
@@ -180,7 +191,18 @@
             ProviderInfo providerInfo = mContext.getPackageManager()
                     .getProviderInfo(provider, GET_META_DATA);
             Bundle metadata = providerInfo.metaData;
-            String startup = mContext.getString(R.string.androidx_startup);
+            discoverAndInitialize(metadata);
+        } catch (PackageManager.NameNotFoundException exception) {
+            throw new StartupException(exception);
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    void discoverAndInitialize(@Nullable Bundle metadata) {
+        String startup = mContext.getString(R.string.androidx_startup);
+        try {
             if (metadata != null) {
                 Set<Class<?>> initializing = new HashSet<>();
                 Set<String> keys = metadata.keySet();
@@ -195,15 +217,17 @@
                             if (StartupLogger.DEBUG) {
                                 StartupLogger.i(String.format("Discovered %s", key));
                             }
-                            doInitialize(component, initializing);
                         }
                     }
                 }
+                // Initialize only after discovery is complete. This way, the check for
+                // isEagerlyInitialized is correct.
+                for (Class<? extends Initializer<?>> component : mDiscovered) {
+                    doInitialize(component, initializing);
+                }
             }
-        } catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {
+        } catch (ClassNotFoundException exception) {
             throw new StartupException(exception);
-        } finally {
-            Trace.endSection();
         }
     }
 }
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt
index 2f9d179..58556c4 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt
@@ -187,7 +187,9 @@
  * to be text which is "start" aligned if there is an icon preset and "start" or "center" aligned if
  * not. label and secondaryLabel contents should be consistently aligned.
  * @param icon A slot for providing the chip's icon. The contents are expected to be a horizontally
- * and vertically aligned icon of size [ChipDefaults.IconSize] or [ChipDefaults.LargeIconSize].
+ * and vertically aligned icon of size [ChipDefaults.IconSize] or [ChipDefaults.LargeIconSize]. In
+ * order to correctly render when the Chip is not enabled the icon must set its alpha value to
+ * [LocalContentAlpha].
  * @param colors [ChipColors] that will be used to resolve the background and content color for
  * this chip in different states. See [ChipDefaults.chipColors]. Defaults to
  * [ChipDefaults.primaryChipColors]
@@ -304,7 +306,9 @@
  * @param label A slot for providing the chip's main label. The contents are expected to be text
  * which is "start" aligned if there is an icon preset and "start" or "center" aligned if not.
  * @param icon A slot for providing the chip's icon. The contents are expected to be a horizontally
- * and vertically aligned icon of size [ChipDefaults.IconSize] or [ChipDefaults.LargeIconSize].
+ * and vertically aligned icon of size [ChipDefaults.IconSize] or [ChipDefaults.LargeIconSize]. In
+ * order to correctly render when the Chip is not enabled the icon must set its alpha value to
+ * [LocalContentAlpha].
  * @param colors [ChipColors] that will be used to resolve the background and content color for
  * this chip in different states. See [ChipDefaults.chipColors]. Defaults to
  * [ChipDefaults.primaryChipColors]
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleChip.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleChip.kt
index 8263cda..5df7922 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleChip.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleChip.kt
@@ -94,12 +94,14 @@
  * @param modifier Modifier to be applied to the chip
  * @param toggleIcon A slot for providing the chip's toggle icon(s). The contents are expected to be
  * a horizontally and vertically centre aligned icon of size [ToggleChipDefaults.IconSize]. Three
- * types of toggle icon are supported and can be obtained from
+ * built-in types of toggle icon are supported and can be obtained from
  * [ToggleChipDefaults.SwitchIcon], [ToggleChipDefaults.RadioIcon] and
- * [ToggleChipDefaults.CheckboxIcon]
+ * [ToggleChipDefaults.CheckboxIcon]. In order to correctly render when the Chip is not enabled the
+ * icon must set its alpha value to [LocalContentAlpha].
  * @param appIcon An optional slot for providing an icon to indicate the purpose of the chip. The
  * contents are expected to be a horizontally and vertically centre aligned icon of size
- * [ToggleChipDefaults.IconSize].
+ * [ToggleChipDefaults.IconSize]. In order to correctly render when the Chip is not enabled the
+ * icon must set its alpha value to [LocalContentAlpha].
  * @param secondaryLabel A slot for providing the chip's secondary label. The contents are expected
  * to be text which is "start" aligned if there is an icon preset and "start" or "center" aligned if
  * not. label and secondaryLabel contents should be consistently aligned.
@@ -268,9 +270,10 @@
  * @param modifier Modifier to be applied to the chip
  * @param toggleIcon A slot for providing the chip's toggle icon(s). The contents are expected to be
  * a horizontally and vertically centre aligned icon of size [ToggleChipDefaults.IconSize]. Three
- * types of toggle icon are supported and can be obtained from
+ * built-in types of toggle icon are supported and can be obtained from
  * [ToggleChipDefaults.SwitchIcon], [ToggleChipDefaults.RadioIcon] and
- * [ToggleChipDefaults.CheckboxIcon]
+ * [ToggleChipDefaults.CheckboxIcon]. In order to correctly render when the Chip is not enabled the
+ * icon must set its alpha value to [LocalContentAlpha].
  * @param secondaryLabel A slot for providing the chip's secondary label. The contents are expected
  * to be "start" or "center" aligned. label and secondaryLabel contents should be consistently
  * aligned.
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoComponents.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoComponents.kt
index 94f4c71..541d9be 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoComponents.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoComponents.kt
@@ -39,6 +39,7 @@
 import androidx.wear.compose.material.Button
 import androidx.wear.compose.material.ButtonDefaults
 import androidx.wear.compose.material.Icon
+import androidx.wear.compose.material.LocalContentAlpha
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.Text
 import kotlin.reflect.KClass
@@ -107,6 +108,7 @@
         contentDescription = null,
         modifier = modifier.size(size),
         contentScale = ContentScale.Crop,
+        alpha = LocalContentAlpha.current
     )
 }
 
@@ -122,8 +124,12 @@
             .requiredSize(32.dp),
         onClick = {},
         colors = ButtonDefaults.buttonColors(
-            disabledBackgroundColor = MaterialTheme.colors.primary,
-            disabledContentColor = MaterialTheme.colors.onPrimary
+            disabledBackgroundColor = MaterialTheme.colors.primary.copy(
+                alpha = LocalContentAlpha.current
+            ),
+            disabledContentColor = MaterialTheme.colors.onPrimary.copy(
+                alpha = LocalContentAlpha.current
+            )
         ),
         enabled = false
     ) {
@@ -136,8 +142,8 @@
             Text(
                 text = text,
                 textAlign = TextAlign.Center,
-                color = MaterialTheme.colors.onPrimary,
-                style = style
+                color = MaterialTheme.colors.onPrimary.copy(alpha = LocalContentAlpha.current),
+                style = style,
             )
         }
     }
diff --git a/wear/tiles/tiles-proto/build.gradle b/wear/tiles/tiles-proto/build.gradle
index 3d52e72..90014de 100644
--- a/wear/tiles/tiles-proto/build.gradle
+++ b/wear/tiles/tiles-proto/build.gradle
@@ -19,7 +19,7 @@
 import androidx.build.Publish
 import androidx.build.RunApiTasks
 import androidx.build.dependencies.DependenciesKt
-import androidx.build.shadow.AndroidXDontIncludeResourceTransformer
+import com.github.jengelman.gradle.plugins.shadow.transformers.DontIncludeResourceTransformer
 
 plugins {
     id("AndroidXPlugin")
@@ -60,8 +60,7 @@
     // library is included from two different downstream libraries. exclude("*.proto") (or
     // **/*.proto etc etc) doesn't exclude the ones from libs.protobufLite, so take a more heavy handed
     // approach and use a transformer to strip those files.
-    // TODO: move back to DontIncludeResourceTransformer.class once Shadow plugin version >6.1.0
-    transform(AndroidXDontIncludeResourceTransformer.class) {
+    transform(DontIncludeResourceTransformer.class) {
         resource = ".proto"
     }
 }
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
index 144992a..435cb4e 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
@@ -1251,6 +1251,28 @@
             interactiveInstance.close()
         }
     }
+
+    @Test
+    fun isConnectionAlive_false_after_close() {
+        val deferredInteractiveInstance = handlerCoroutineScope.async {
+            service.getOrCreateInteractiveWatchFaceClient(
+                "testId",
+                deviceConfig,
+                systemState,
+                null,
+                complications
+            )
+        }
+
+        // Create the engine which triggers creation of InteractiveWatchFaceClient.
+        createEngine()
+
+        val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+        assertThat(interactiveInstance.isConnectionAlive()).isTrue()
+
+        interactiveInstance.close()
+        assertThat(interactiveInstance.isConnectionAlive()).isFalse()
+    }
 }
 
 internal class TestExampleCanvasAnalogWatchFaceService(
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
index 3f7b551..1a3a85a 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
@@ -257,6 +257,7 @@
     private val readyListeners =
         HashMap<InteractiveWatchFaceClient.OnWatchFaceReadyListener, Executor>()
     private var watchfaceReadyListenerRegistered = false
+    private var closed = false
 
     init {
         iInteractiveWatchFace.asBinder().linkToDeath(
@@ -347,6 +348,9 @@
 
     override fun close() = TraceEvent("InteractiveWatchFaceClientImpl.close").use {
         iInteractiveWatchFace.release()
+        synchronized(lock) {
+            closed = true
+        }
     }
 
     override fun sendTouchEvent(
@@ -405,7 +409,8 @@
         }
     }
 
-    override fun isConnectionAlive() = iInteractiveWatchFace.asBinder().isBinderAlive
+    override fun isConnectionAlive() =
+        iInteractiveWatchFace.asBinder().isBinderAlive && synchronized(lock) { !closed }
 
     private fun registerWatchfaceReadyListener() {
         if (watchfaceReadyListenerRegistered) {
diff --git a/work/work-runtime/lint-baseline.xml b/work/work-runtime/lint-baseline.xml
index bb2be82..e0e0f58 100644
--- a/work/work-runtime/lint-baseline.xml
+++ b/work/work-runtime/lint-baseline.xml
@@ -388,6 +388,17 @@
 
     <issue
         id="ClassVerificationFailure"
+        message="This call references a method added in API level 31; however, the containing class androidx.work.impl.background.systemjob.SystemJobInfoConverter is reachable from earlier API levels and will fail run-time class verification."
+        errorLine1="            builder.setExpedited(true);"
+        errorLine2="                    ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java"
+            line="130"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="ClassVerificationFailure"
         message="This call references a method added in API level 24; however, the containing class androidx.work.impl.background.systemjob.SystemJobInfoConverter is reachable from earlier API levels and will fail run-time class verification."
         errorLine1="        return new JobInfo.TriggerContentUri(trigger.getUri(), flag);"
         errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
index d808d72..922dec2 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
@@ -129,19 +129,17 @@
                     if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {
                         // Ignore requests that have an idle mode constraint.
                         Logger.get().debug(TAG,
-                                String.format("Ignoring WorkSpec %s, Requires device idle.",
-                                        workSpec));
+                                "Ignoring " + workSpec + ". Requires device idle.");
                     } else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
                         // Ignore requests that have content uri triggers.
                         Logger.get().debug(TAG,
-                                String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",
-                                        workSpec));
+                                "Ignoring " + workSpec + ". Requires ContentUri triggers.");
                     } else {
                         constrainedWorkSpecs.add(workSpec);
                         constrainedWorkSpecIds.add(workSpec.id);
                     }
                 } else {
-                    Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));
+                    Logger.get().debug(TAG, "Starting work for " + workSpec.id);
                     mWorkManagerImpl.startWork(workSpec.id);
                 }
             }
@@ -151,8 +149,8 @@
         // WorkSpecs. Therefore we need to lock here.
         synchronized (mLock) {
             if (!constrainedWorkSpecs.isEmpty()) {
-                Logger.get().debug(TAG, String.format("Starting tracking for [%s]",
-                        TextUtils.join(",", constrainedWorkSpecIds)));
+                String formattedIds = TextUtils.join(",", constrainedWorkSpecIds);
+                Logger.get().debug(TAG, "Starting tracking for " + formattedIds);
                 mConstrainedWorkSpecs.addAll(constrainedWorkSpecs);
                 mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
             }
@@ -176,7 +174,7 @@
         }
 
         registerExecutionListenerIfNeeded();
-        Logger.get().debug(TAG, String.format("Cancelling work ID %s", workSpecId));
+        Logger.get().debug(TAG, "Cancelling work ID " + workSpecId);
         if (mDelayedWorkTracker != null) {
             mDelayedWorkTracker.unschedule(workSpecId);
         }
@@ -187,9 +185,7 @@
     @Override
     public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
         for (String workSpecId : workSpecIds) {
-            Logger.get().debug(
-                    TAG,
-                    String.format("Constraints met: Scheduling work ID %s", workSpecId));
+            Logger.get().debug(TAG, "Constraints met: Scheduling work ID " + workSpecId);
             mWorkManagerImpl.startWork(workSpecId);
         }
     }
@@ -197,8 +193,7 @@
     @Override
     public void onAllConstraintsNotMet(@NonNull List<String> workSpecIds) {
         for (String workSpecId : workSpecIds) {
-            Logger.get().debug(TAG,
-                    String.format("Constraints not met: Cancelling work ID %s", workSpecId));
+            Logger.get().debug(TAG, "Constraints not met: Cancelling work ID " + workSpecId);
             mWorkManagerImpl.stopWork(workSpecId);
         }
     }
@@ -217,7 +212,7 @@
             // executor thread.
             for (WorkSpec constrainedWorkSpec : mConstrainedWorkSpecs) {
                 if (constrainedWorkSpec.id.equals(workSpecId)) {
-                    Logger.get().debug(TAG, String.format("Stopping tracking for %s", workSpecId));
+                    Logger.get().debug(TAG, "Stopping tracking for " + workSpecId);
                     mConstrainedWorkSpecs.remove(constrainedWorkSpec);
                     mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
                     break;