Merge changes from topic "kt-method" into androidx-main

* changes:
  Convert MethodCallsLogger to Kotlin
  Rename MethodCallsLogger.java to .kt
diff --git a/OWNERS b/OWNERS
index b6e6906..12e1923 100644
--- a/OWNERS
+++ b/OWNERS
@@ -28,3 +28,6 @@
 per-file *settings.gradle = set noparent
 per-file *settings.gradle = [email protected], [email protected], [email protected]
 per-file *libraryversions.toml = [email protected]
+
+# Copybara can self-approve CLs within synced docs.
+per-file docs/... = [email protected]
\ No newline at end of file
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
index c7bc830..375f3519 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
@@ -46,6 +46,9 @@
         if (Features.ADD_PERMISSIONS_AND_GET_VISIBILITY.equals(feature)) {
             return true;
         }
+        if (Features.TOKENIZER_TYPE_RFC822.equals(feature)) {
+            return true;
+        }
         return false;
     }
 }
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
index 6be85af..1063c54 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
@@ -1481,7 +1481,8 @@
                     new SearchSuggestionSpecToProtoConverter(suggestionQueryExpression,
                             searchSuggestionSpec,
                             Collections.singleton(prefix),
-                            mNamespaceMapLocked);
+                            mNamespaceMapLocked,
+                            mSchemaMapLocked);
 
             if (searchSuggestionSpecToProtoConverter.hasNothingToSearch()) {
                 // there is nothing to search over given their search filters, so we can return an
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java
index df8c392..cc6a1b5 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSuggestionSpecToProtoConverter.java
@@ -21,10 +21,13 @@
 import androidx.appsearch.app.SearchSuggestionSpec;
 import androidx.core.util.Preconditions;
 
+import com.google.android.icing.proto.SchemaTypeConfigProto;
 import com.google.android.icing.proto.SuggestionScoringSpecProto;
 import com.google.android.icing.proto.SuggestionSpecProto;
 import com.google.android.icing.proto.TermMatchType;
+import com.google.android.icing.proto.TypePropertyMask;
 
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -38,10 +41,20 @@
     private final String mSuggestionQueryExpression;
     private final SearchSuggestionSpec mSearchSuggestionSpec;
     /**
+     * The client specific packages and databases to search for. For local storage, this always
+     * contains a single prefix.
+     */
+    private final Set<String> mPrefixes;
+    /**
      * The intersected prefixed namespaces that are existing in AppSearch and also accessible to the
      * client.
      */
     private final Set<String> mTargetPrefixedNamespaceFilters;
+    /**
+     * The intersected prefixed schema types that are existing in AppSearch and also accessible to
+     * the client.
+     */
+    private final Set<String> mTargetPrefixedSchemaFilters;
 
     /**
      * Creates a {@link SearchSuggestionSpecToProtoConverter} for given
@@ -58,14 +71,18 @@
             @NonNull String suggestionQueryExpression,
             @NonNull SearchSuggestionSpec searchSuggestionSpec,
             @NonNull Set<String> prefixes,
-            @NonNull Map<String, Set<String>> namespaceMap) {
+            @NonNull Map<String, Set<String>> namespaceMap,
+            @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap) {
         mSuggestionQueryExpression = Preconditions.checkNotNull(suggestionQueryExpression);
         mSearchSuggestionSpec = Preconditions.checkNotNull(searchSuggestionSpec);
-        Preconditions.checkNotNull(prefixes);
+        mPrefixes = Preconditions.checkNotNull(prefixes);
         Preconditions.checkNotNull(namespaceMap);
         mTargetPrefixedNamespaceFilters =
                 SearchSpecToProtoConverterUtil.generateTargetNamespaceFilters(
                         prefixes, namespaceMap, searchSuggestionSpec.getFilterNamespaces());
+        mTargetPrefixedSchemaFilters =
+                SearchSpecToProtoConverterUtil.generateTargetSchemaFilters(
+                        prefixes, schemaMap, searchSuggestionSpec.getFilterSchemas());
     }
 
     /**
@@ -73,7 +90,7 @@
      * should skip send request to Icing.
      */
     public boolean hasNothingToSearch() {
-        return mTargetPrefixedNamespaceFilters.isEmpty();
+        return mTargetPrefixedNamespaceFilters.isEmpty() || mTargetPrefixedSchemaFilters.isEmpty();
     }
 
     /**
@@ -88,8 +105,23 @@
         SuggestionSpecProto.Builder protoBuilder = SuggestionSpecProto.newBuilder()
                 .setPrefix(mSuggestionQueryExpression)
                 .addAllNamespaceFilters(mTargetPrefixedNamespaceFilters)
+                .addAllSchemaTypeFilters(mTargetPrefixedSchemaFilters)
                 .setNumToReturn(mSearchSuggestionSpec.getMaximumResultCount());
 
+        // Convert type property filter map into type property mask proto.
+        for (Map.Entry<String, List<String>> entry :
+                mSearchSuggestionSpec.getFilterProperties().entrySet()) {
+            for (String prefix : mPrefixes) {
+                String prefixedSchemaType = prefix + entry.getKey();
+                if (mTargetPrefixedSchemaFilters.contains(prefixedSchemaType)) {
+                    protoBuilder.addTypePropertyFilters(TypePropertyMask.newBuilder()
+                            .setSchemaType(prefixedSchemaType)
+                            .addAllPaths(entry.getValue())
+                            .build());
+                }
+            }
+        }
+
         // TODO(b/227356108) expose setTermMatch in SearchSuggestionSpec.
         protoBuilder.setScoringSpec(SuggestionScoringSpecProto.newBuilder()
                 .setScoringMatchType(TermMatchType.Code.EXACT_ONLY)
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
index 336b213..47fad2c 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
@@ -44,6 +44,11 @@
         if (Features.ADD_PERMISSIONS_AND_GET_VISIBILITY.equals(feature)) {
             return BuildCompat.isAtLeastT();
         }
+        // TODO: Update to reflect support in Android U+ once this feature is synced over into
+        //  service-appsearch
+        if (Features.TOKENIZER_TYPE_RFC822.equals(feature)) {
+            return false;
+        }
         return false;
     }
 }
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 8580d6f..935fa28 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -165,6 +165,7 @@
     field public static final int INDEXING_TYPE_PREFIXES = 2; // 0x2
     field public static final int TOKENIZER_TYPE_NONE = 0; // 0x0
     field public static final int TOKENIZER_TYPE_PLAIN = 1; // 0x1
+    field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.TOKENIZER_TYPE_RFC822) public static final int TOKENIZER_TYPE_RFC822 = 3; // 0x3
   }
 
   public static final class AppSearchSchema.StringPropertyConfig.Builder {
@@ -205,6 +206,7 @@
     field public static final String GLOBAL_SEARCH_SESSION_GET_SCHEMA = "GLOBAL_SEARCH_SESSION_GET_SCHEMA";
     field public static final String GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK = "GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK";
     field public static final String SEARCH_RESULT_MATCH_INFO_SUBMATCH = "SEARCH_RESULT_MATCH_INFO_SUBMATCH";
+    field public static final String TOKENIZER_TYPE_RFC822 = "TOKENIZER_TYPE_RFC822";
   }
 
   public class GenericDocument {
diff --git a/appsearch/appsearch/api/public_plus_experimental_current.txt b/appsearch/appsearch/api/public_plus_experimental_current.txt
index 8580d6f..935fa28 100644
--- a/appsearch/appsearch/api/public_plus_experimental_current.txt
+++ b/appsearch/appsearch/api/public_plus_experimental_current.txt
@@ -165,6 +165,7 @@
     field public static final int INDEXING_TYPE_PREFIXES = 2; // 0x2
     field public static final int TOKENIZER_TYPE_NONE = 0; // 0x0
     field public static final int TOKENIZER_TYPE_PLAIN = 1; // 0x1
+    field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.TOKENIZER_TYPE_RFC822) public static final int TOKENIZER_TYPE_RFC822 = 3; // 0x3
   }
 
   public static final class AppSearchSchema.StringPropertyConfig.Builder {
@@ -205,6 +206,7 @@
     field public static final String GLOBAL_SEARCH_SESSION_GET_SCHEMA = "GLOBAL_SEARCH_SESSION_GET_SCHEMA";
     field public static final String GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK = "GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK";
     field public static final String SEARCH_RESULT_MATCH_INFO_SUBMATCH = "SEARCH_RESULT_MATCH_INFO_SUBMATCH";
+    field public static final String TOKENIZER_TYPE_RFC822 = "TOKENIZER_TYPE_RFC822";
   }
 
   public class GenericDocument {
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 8580d6f..935fa28 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -165,6 +165,7 @@
     field public static final int INDEXING_TYPE_PREFIXES = 2; // 0x2
     field public static final int TOKENIZER_TYPE_NONE = 0; // 0x0
     field public static final int TOKENIZER_TYPE_PLAIN = 1; // 0x1
+    field @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.TOKENIZER_TYPE_RFC822) public static final int TOKENIZER_TYPE_RFC822 = 3; // 0x3
   }
 
   public static final class AppSearchSchema.StringPropertyConfig.Builder {
@@ -205,6 +206,7 @@
     field public static final String GLOBAL_SEARCH_SESSION_GET_SCHEMA = "GLOBAL_SEARCH_SESSION_GET_SCHEMA";
     field public static final String GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK = "GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK";
     field public static final String SEARCH_RESULT_MATCH_INFO_SUBMATCH = "SEARCH_RESULT_MATCH_INFO_SUBMATCH";
+    field public static final String TOKENIZER_TYPE_RFC822 = "TOKENIZER_TYPE_RFC822";
   }
 
   public class GenericDocument {
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
index b3cc00b..4de83dc 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
@@ -24,6 +24,7 @@
 import androidx.appsearch.app.AppSearchSchema.PropertyConfig;
 import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.junit.After;
@@ -178,7 +179,7 @@
                         .build()).get();
         assertThat(suggestions).containsExactly(resultFoo, resultFool);
 
-        // non exist namespace has 2 results.
+        // non exist namespace has empty result
         suggestions = mDb1.searchSuggestionAsync(
                 /*suggestionQueryExpression=*/"f",
                 new SearchSuggestionSpec.Builder(/*totalResultCount=*/10)
@@ -187,6 +188,185 @@
     }
 
     @Test
+    public void testSearchSuggestion_schemaFilter() throws Exception {
+        // Schema registration
+        AppSearchSchema schemaType1 = new AppSearchSchema.Builder("Type1").addProperty(
+                        new StringPropertyConfig.Builder("body")
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                .build())
+                .build();
+        AppSearchSchema schemaType2 = new AppSearchSchema.Builder("Type2").addProperty(
+                        new StringPropertyConfig.Builder("body")
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                .build())
+                .build();
+        AppSearchSchema schemaType3 = new AppSearchSchema.Builder("Type3").addProperty(
+                        new StringPropertyConfig.Builder("body")
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                .build())
+                .build();
+        mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
+                .addSchemas(schemaType1, schemaType2, schemaType3).build()).get();
+
+        // Index documents
+        GenericDocument doc1 = new GenericDocument.Builder<>("namespace", "id1", "Type1")
+                .setPropertyString("body", "fo foo")
+                .build();
+        GenericDocument doc2 = new GenericDocument.Builder<>("namespace", "id2", "Type2")
+                .setPropertyString("body", "foo")
+                .build();
+        GenericDocument doc3 = new GenericDocument.Builder<>("namespace", "id3", "Type3")
+                .setPropertyString("body", "fool")
+                .build();
+
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder().addGenericDocuments(doc1, doc2, doc3).build()));
+
+        SearchSuggestionResult resultFo =
+                new SearchSuggestionResult.Builder().setSuggestedResult("fo").build();
+        SearchSuggestionResult resultFoo =
+                new SearchSuggestionResult.Builder().setSuggestedResult("foo").build();
+        SearchSuggestionResult resultFool =
+                new SearchSuggestionResult.Builder().setSuggestedResult("fool").build();
+
+        // Type1 has 2 results.
+        List<SearchSuggestionResult> suggestions = mDb1.searchSuggestionAsync(
+                /*suggestionQueryExpression=*/"f",
+                new SearchSuggestionSpec.Builder(/*totalResultCount=*/10)
+                        .addFilterSchemas("Type1").build()).get();
+        assertThat(suggestions).containsExactly(resultFoo, resultFo).inOrder();
+
+        // Type2 has 1 result.
+        suggestions = mDb1.searchSuggestionAsync(
+                /*suggestionQueryExpression=*/"f",
+                new SearchSuggestionSpec.Builder(/*totalResultCount=*/10)
+                        .addFilterSchemas("Type2").build()).get();
+        assertThat(suggestions).containsExactly(resultFoo).inOrder();
+
+        // Type2 and 3 has 2 results.
+        suggestions = mDb1.searchSuggestionAsync(
+                /*suggestionQueryExpression=*/"f",
+                new SearchSuggestionSpec.Builder(/*totalResultCount=*/10)
+                        .addFilterSchemas("Type2", "Type3")
+                        .build()).get();
+        assertThat(suggestions).containsExactly(resultFoo, resultFool);
+
+        // non exist type has empty result.
+        suggestions = mDb1.searchSuggestionAsync(
+                /*suggestionQueryExpression=*/"f",
+                new SearchSuggestionSpec.Builder(/*totalResultCount=*/10)
+                        .addFilterSchemas("nonExistType").build()).get();
+        assertThat(suggestions).isEmpty();
+    }
+
+    @Test
+    public void testSearchSuggestion_propertyFilter() throws Exception {
+        // Schema registration
+        AppSearchSchema schemaType1 = new AppSearchSchema.Builder("Type1").addProperty(
+                        new StringPropertyConfig.Builder("propertyone")
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                .build()).addProperty(
+                        new StringPropertyConfig.Builder("propertytwo")
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                .build())
+                .build();
+        AppSearchSchema schemaType2 = new AppSearchSchema.Builder("Type2").addProperty(
+                        new StringPropertyConfig.Builder("propertythree")
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                .build()).addProperty(
+                        new StringPropertyConfig.Builder("propertyfour")
+                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                .build())
+                .build();
+        mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
+                .addSchemas(schemaType1, schemaType2).build()).get();
+
+        // Index documents
+        GenericDocument doc1 = new GenericDocument.Builder<>("namespace", "id1", "Type1")
+                .setPropertyString("propertyone", "termone")
+                .setPropertyString("propertytwo", "termtwo")
+                .build();
+        GenericDocument doc2 = new GenericDocument.Builder<>("namespace", "id2", "Type2")
+                .setPropertyString("propertythree", "termthree")
+                .setPropertyString("propertyfour", "termfour")
+                .build();
+
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder().addGenericDocuments(doc1, doc2).build()));
+
+        SearchSuggestionResult resultOne =
+                new SearchSuggestionResult.Builder().setSuggestedResult("termone").build();
+        SearchSuggestionResult resultTwo =
+                new SearchSuggestionResult.Builder().setSuggestedResult("termtwo").build();
+        SearchSuggestionResult resultThree =
+                new SearchSuggestionResult.Builder().setSuggestedResult("termthree").build();
+        SearchSuggestionResult resultFour =
+                new SearchSuggestionResult.Builder().setSuggestedResult("termfour").build();
+
+        // Only search for type1/propertyone
+        List<SearchSuggestionResult> suggestions = mDb1.searchSuggestionAsync(
+                /*suggestionQueryExpression=*/"t",
+                new SearchSuggestionSpec.Builder(/*totalResultCount=*/10)
+                        .addFilterSchemas("Type1")
+                        .addFilterProperties("Type1", ImmutableList.of("propertyone"))
+                        .build()).get();
+        assertThat(suggestions).containsExactly(resultOne);
+
+        // Only search for type1/propertyone and type1/propertytwo
+        suggestions = mDb1.searchSuggestionAsync(
+                /*suggestionQueryExpression=*/"t",
+                new SearchSuggestionSpec.Builder(/*totalResultCount=*/10)
+                        .addFilterSchemas("Type1")
+                        .addFilterProperties("Type1",
+                                ImmutableList.of("propertyone", "propertytwo"))
+                        .build()).get();
+        assertThat(suggestions).containsExactly(resultOne, resultTwo);
+
+        // Only search for type1/propertyone and type2/propertythree
+        suggestions = mDb1.searchSuggestionAsync(
+                /*suggestionQueryExpression=*/"t",
+                new SearchSuggestionSpec.Builder(/*totalResultCount=*/10)
+                        .addFilterSchemas("Type1", "Type2")
+                        .addFilterProperties("Type1", ImmutableList.of("propertyone"))
+                        .addFilterProperties("Type2", ImmutableList.of("propertythree"))
+                        .build()).get();
+        assertThat(suggestions).containsExactly(resultOne, resultThree);
+
+        // Only search for type1/propertyone and type2/propertyfour, in addFilterPropertyPaths
+        suggestions = mDb1.searchSuggestionAsync(
+                /*suggestionQueryExpression=*/"t",
+                new SearchSuggestionSpec.Builder(/*totalResultCount=*/10)
+                        .addFilterSchemas("Type1", "Type2")
+                        .addFilterProperties("Type1", ImmutableList.of("propertyone"))
+                        .addFilterPropertyPaths("Type2",
+                                ImmutableList.of(new PropertyPath("propertyfour")))
+                        .build()).get();
+        assertThat(suggestions).containsExactly(resultOne, resultFour);
+
+        // Only search for type1/propertyone and everything in type2
+        suggestions = mDb1.searchSuggestionAsync(
+                /*suggestionQueryExpression=*/"t",
+                new SearchSuggestionSpec.Builder(/*totalResultCount=*/10)
+                        .addFilterProperties("Type1", ImmutableList.of("propertyone"))
+                        .build()).get();
+        assertThat(suggestions).containsExactly(resultOne, resultThree, resultFour);
+    }
+
+    @Test
     public void testSearchSuggestion_differentPrefix() throws Exception {
         // Schema registration
         AppSearchSchema schema = new AppSearchSchema.Builder("Type").addProperty(
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionPlatformInternalTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionPlatformInternalTest.java
index 00ad136..eb13b1e 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionPlatformInternalTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionPlatformInternalTest.java
@@ -90,4 +90,16 @@
     public void testSearchSuggestion_ignoreOperators() throws Exception {
         // TODO(b/227356108) enable the test when suggestion is ready in platform.
     }
+
+    @Override
+    @Test
+    public void testSearchSuggestion_schemaFilter() throws Exception {
+        // TODO(b/227356108) enable the test when suggestion is ready in platform.
+    }
+
+    @Override
+    @Test
+    public void testSearchSuggestion_propertyFilter() throws Exception {
+        // TODO(b/227356108) enable the test when suggestion is ready in platform.
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSuggestionSpecTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSuggestionSpecTest.java
index 0cc45ff..dba953a 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSuggestionSpecTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSuggestionSpecTest.java
@@ -16,8 +16,14 @@
 
 package androidx.appsearch.app;
 
+import static androidx.appsearch.app.AppSearchResult.RESULT_INVALID_ARGUMENT;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
+import androidx.appsearch.exceptions.AppSearchException;
+
 import com.google.common.collect.ImmutableList;
 
 import org.junit.Test;
@@ -25,7 +31,7 @@
 // TODO(b/227356108): move this test to cts test once we un-hide search suggestion API.
 public class SearchSuggestionSpecTest {
     @Test
-    public void testBuildDefaultSearchSuggestionSpec() {
+    public void testBuildDefaultSearchSuggestionSpec() throws Exception {
         SearchSuggestionSpec searchSuggestionSpec =
                 new SearchSuggestionSpec.Builder(/*totalResultCount=*/123).build();
         assertThat(searchSuggestionSpec.getMaximumResultCount()).isEqualTo(123);
@@ -33,34 +39,70 @@
     }
 
     @Test
-    public void testBuildSearchSuggestionSpec() {
+    public void testBuildSearchSuggestionSpec() throws Exception {
         SearchSuggestionSpec searchSuggestionSpec =
                 new SearchSuggestionSpec.Builder(/*totalResultCount=*/123)
                         .addFilterNamespaces("namespace1", "namespace2")
                         .addFilterNamespaces(ImmutableList.of("namespace3"))
+                        .addFilterSchemas("Person", "Email")
+                        .addFilterSchemas(ImmutableList.of("Foo"))
+                        .addFilterProperties("Email", ImmutableList.of("Subject", "body"))
+                        .addFilterPropertyPaths("Foo",
+                                ImmutableList.of(new PropertyPath("Bar")))
                         .build();
 
         assertThat(searchSuggestionSpec.getMaximumResultCount()).isEqualTo(123);
         assertThat(searchSuggestionSpec.getFilterNamespaces())
                 .containsExactly("namespace1", "namespace2", "namespace3");
+        assertThat(searchSuggestionSpec.getFilterSchemas())
+                .containsExactly("Person", "Email", "Foo");
+        assertThat(searchSuggestionSpec.getFilterProperties())
+                .containsExactly("Email",  ImmutableList.of("Subject", "body"),
+                        "Foo",  ImmutableList.of("Bar"));
     }
 
     @Test
-    public void testRebuild() {
+    public void testPropertyFilterMustMatchSchemaFilter() throws Exception {
+        AppSearchException e = assertThrows(AppSearchException.class,
+                () -> new SearchSuggestionSpec.Builder(/*totalResultCount=*/123)
+                        .addFilterSchemas("Person")
+                        .addFilterProperties("Email", ImmutableList.of("Subject", "body"))
+                        .build());
+        assertThat(e.getResultCode()).isEqualTo(RESULT_INVALID_ARGUMENT);
+        assertThat(e).hasMessageThat().contains("The schema: Email exists in the "
+                + "property filter but doesn't exist in the schema filter.");
+    }
+
+    @Test
+    public void testRebuild() throws Exception {
         SearchSuggestionSpec.Builder builder =
                 new SearchSuggestionSpec.Builder(/*totalResultCount=*/123)
-                        .addFilterNamespaces("namespace1", "namespace2");
+                        .addFilterNamespaces("namespace1", "namespace2")
+                        .addFilterSchemas("Person", "Email")
+                        .addFilterProperties("Email", ImmutableList.of("Subject", "body"));
 
         SearchSuggestionSpec original = builder.build();
 
-        builder.addFilterNamespaces("namespace3", "namespace4");
+        builder.addFilterNamespaces("namespace3", "namespace4")
+                .addFilterSchemas("Message", "Foo")
+                .addFilterProperties("Foo", ImmutableList.of("Bar"));
         SearchSuggestionSpec rebuild = builder.build();
 
         assertThat(original.getMaximumResultCount()).isEqualTo(123);
         assertThat(original.getFilterNamespaces())
                 .containsExactly("namespace1", "namespace2");
+        assertThat(original.getFilterSchemas())
+                .containsExactly("Person", "Email");
+        assertThat(original.getFilterProperties())
+                .containsExactly("Email",  ImmutableList.of("Subject", "body"));
+
         assertThat(rebuild.getMaximumResultCount()).isEqualTo(123);
         assertThat(rebuild.getFilterNamespaces())
                 .containsExactly("namespace1", "namespace2", "namespace3", "namespace4");
+        assertThat(rebuild.getFilterSchemas())
+                .containsExactly("Person", "Email", "Message", "Foo");
+        assertThat(rebuild.getFilterProperties())
+                .containsExactly("Email",  ImmutableList.of("Subject", "body"),
+                        "Foo",  ImmutableList.of("Bar"));
     }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
index 1417929..d08a89d 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
@@ -368,4 +368,14 @@
 
         assertThat(schemaString).isEqualTo(expectedString);
     }
+
+    @Test
+    public void testStringPropertyConfig_setTokenizerType() {
+        assertThrows(IllegalArgumentException.class, () ->
+                new StringPropertyConfig.Builder("subject").setTokenizerType(5).build());
+        assertThrows(IllegalArgumentException.class, () ->
+                new StringPropertyConfig.Builder("subject").setTokenizerType(2).build());
+        assertThrows(IllegalArgumentException.class, () ->
+                new StringPropertyConfig.Builder("subject").setTokenizerType(-1).build());
+    }
 }
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 bc1e02c..751ee86 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
@@ -3406,4 +3406,54 @@
             assertThat(matches.get(0).getSubmatch()).isEqualTo("🐟");
         }
     }
+
+    @Test
+    public void testRfc822() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.TOKENIZER_TYPE_RFC822));
+        AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
+                .addProperty(new StringPropertyConfig.Builder("address")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_RFC822)
+                        .build()
+                ).build();
+        mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
+                .setForceOverride(true).addSchemas(emailSchema).build()).get();
+
+        GenericDocument email = new GenericDocument.Builder<>("NS", "alex1", "Email")
+                .setPropertyString("address", "Alex Saveliev <[email protected]>")
+                .build();
+        mDb1.putAsync(new PutDocumentsRequest.Builder().addGenericDocuments(email).build()).get();
+
+        SearchResults sr = mDb1.search("com", new SearchSpec.Builder().build());
+        List<SearchResult> page = sr.getNextPageAsync().get();
+
+        // RFC tokenization will produce the following tokens for
+        // "Alex Saveliev <[email protected]>" : ["Alex Saveliev <[email protected]>", "Alex",
+        // "Saveliev", "alex.sav", "[email protected]", "alex.sav", "google", "com"]. Therefore,
+        // a query for "com" should match the document.
+        assertThat(page).hasSize(1);
+        assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("alex1");
+
+        // Plain tokenizer will not match this
+        AppSearchSchema plainEmailSchema = new AppSearchSchema.Builder("Email")
+                .addProperty(new StringPropertyConfig.Builder("address")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+
+        // Flipping the tokenizer type is a backwards compatible change. The index will be
+        // rebuilt with the email doc being tokenized in the new way.
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder().addSchemas(plainEmailSchema).build()).get();
+
+        sr = mDb1.search("com", new SearchSpec.Builder().build());
+
+        // Plain tokenization will produce the following tokens for
+        // "Alex Saveliev <[email protected]>" : ["Alex", "Saveliev", "<", "alex.sav",
+        // "google.com", ">"]. So "com" will not match any of the tokens produced.
+        assertThat(sr.getNextPageAsync().get()).hasSize(0);
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java
index dd47914..2086796 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SetSchemaRequestCtsTest.java
@@ -41,6 +41,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -780,4 +781,30 @@
                         ImmutableSet.of(SetSchemaRequest.READ_SMS, SetSchemaRequest.READ_CALENDAR),
                         ImmutableSet.of(SetSchemaRequest.READ_HOME_APP_SEARCH_DATA)));
     }
+
+    @Test
+    public void testRfc822TokenizerType() {
+        AppSearchSchema.StringPropertyConfig prop1 =
+                new AppSearchSchema.StringPropertyConfig.Builder("prop1")
+                        .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(
+                                AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                        .setTokenizerType(
+                                AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_RFC822)
+                        .build();
+        AppSearchSchema schema1 =
+                new AppSearchSchema.Builder("type1").addProperty(prop1).build();
+
+        SetSchemaRequest request = new SetSchemaRequest.Builder()
+                .addSchemas(schema1)
+                .setForceOverride(true)
+                .setVersion(142857)
+                .build();
+        AppSearchSchema[] schemas = request.getSchemas().toArray(new AppSearchSchema[0]);
+        assertThat(schemas).hasLength(1);
+        List<AppSearchSchema.PropertyConfig> properties = schemas[0].getProperties();
+        assertThat(properties).hasSize(1);
+        assertThat(((AppSearchSchema.StringPropertyConfig) properties.get(0)).getTokenizerType())
+                .isEqualTo(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_RFC822);
+    }
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
index 1a19c65..cce0237 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
@@ -21,6 +21,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresFeature;
 import androidx.annotation.RestrictTo;
 import androidx.appsearch.exceptions.IllegalSchemaException;
 import androidx.appsearch.util.BundleUtil;
@@ -482,6 +483,7 @@
         @IntDef(value = {
                 TOKENIZER_TYPE_NONE,
                 TOKENIZER_TYPE_PLAIN,
+                TOKENIZER_TYPE_RFC822
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface TokenizerType {}
@@ -507,6 +509,25 @@
          */
         public static final int TOKENIZER_TYPE_PLAIN = 1;
 
+        // TODO(b/204333391): In icing, the "2" tokenizer is the verbatim tokenizer.
+
+        /**
+         * Tokenization for emails. This value indicates that tokens should be extracted from
+         * this property based on email structure.
+         *
+         * <p>Ex. A property with "[email protected]" will produce tokens for "alex", "sav",
+         * "alex.sav", "google", "com", and "[email protected]"
+         *
+         * <p>It is only valid for tokenizer_type to be 'RFC822' if {@link #getIndexingType} is
+         * {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}.
+         */
+// @exportToFramework:startStrip()
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.TOKENIZER_TYPE_RFC822)
+// @exportToFramework:endStrip()
+        public static final int TOKENIZER_TYPE_RFC822 = 3;
+
         StringPropertyConfig(@NonNull Bundle bundle) {
             super(bundle);
         }
@@ -576,8 +597,13 @@
              */
             @NonNull
             public StringPropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
-                Preconditions.checkArgumentInRange(
-                        tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_PLAIN, "tokenizerType");
+                // TODO(b/204333391): Change to checkArgumentInRange once verbatim is supported
+                if (tokenizerType != TOKENIZER_TYPE_NONE && tokenizerType != TOKENIZER_TYPE_PLAIN
+                        && tokenizerType != TOKENIZER_TYPE_RFC822) {
+                    throw new IllegalArgumentException("Tokenizer value " + tokenizerType + " is "
+                            + "out of range. Valid values are TOKENIZER_TYPE_NONE, "
+                            + "TOKENIZER_TYPE_PLAIN, and TOKENIZER_TYPE_RFC822");
+                }
                 mTokenizerType = tokenizerType;
                 return this;
             }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
index dc530ee..5edc780 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
@@ -69,6 +69,12 @@
     String ADD_PERMISSIONS_AND_GET_VISIBILITY = "ADD_PERMISSIONS_AND_GET_VISIBILITY";
 
     /**
+     * Feature for {@link #isFeatureSupported(String)}. This feature covers
+     * {@link AppSearchSchema.StringPropertyConfig#TOKENIZER_TYPE_RFC822}.
+     */
+    String TOKENIZER_TYPE_RFC822 = "TOKENIZER_TYPE_RFC822";
+
+    /**
      * Returns whether a feature is supported at run-time. Feature support depends on the
      * feature in question, the AppSearch backend being used and the Android version of the
      * device.
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 74153b5..17e326e 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResult.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResult.java
@@ -593,8 +593,8 @@
              * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc.
              * For class example 1 this returns "subject".
              *
-             * @param propertyPath A {@code dot-delimited sequence of property names indicating
-             *                     which property in the document these snippets correspond to.
+             * @param propertyPath A dot-delimited sequence of property names indicating which
+             *                     property in the document these snippets correspond to.
              */
             public Builder(@NonNull String propertyPath) {
                 mPropertyPath = Preconditions.checkNotNull(propertyPath);
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
index 07e709a..7dae734 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
@@ -293,6 +293,8 @@
      *
      * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned
      * by this function, rather than calling it multiple times.
+     *
+     * @return A mapping of schema types to lists of projection strings.
      */
     @NonNull
     public Map<String, List<String>> getProjections() {
@@ -314,6 +316,8 @@
      *
      * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned
      * by this function, rather than calling it multiple times.
+     *
+     * @return A mapping of schema types to lists of projection {@link PropertyPath} objects.
      */
     @NonNull
     public Map<String, List<PropertyPath>> getProjectionPaths() {
@@ -624,6 +628,9 @@
          * it will be ignored for that result. Property paths cannot be null.
          *
          * @see #addProjectionPaths
+         *
+         * @param schema a string corresponding to the schema to add projections to.
+         * @param propertyPaths the projections to add.
          */
         @NonNull
         public SearchSpec.Builder addProjection(
@@ -699,6 +706,9 @@
          *   subject: "IMPORTANT"
          * }
          * }</pre>
+         *
+         * @param schema a string corresponding to the schema to add projections to.
+         * @param propertyPaths the projections to add.
          */
         @NonNull
         public SearchSpec.Builder addProjectionPaths(
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java
index d18f9cf..16611e4 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSuggestionSpec.java
@@ -15,12 +15,20 @@
  */
 
 package androidx.appsearch.app;
+import static androidx.appsearch.app.AppSearchResult.RESULT_INVALID_ARGUMENT;
+
+import android.annotation.SuppressLint;
 import android.os.Bundle;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
+import androidx.appsearch.annotation.Document;
+import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.util.BundleUtil;
+import androidx.collection.ArrayMap;
+import androidx.collection.ArraySet;
 import androidx.core.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -30,6 +38,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * This class represents the specification logic for AppSearch. It can be used to set the filter
@@ -41,6 +51,8 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class SearchSuggestionSpec {
     static final String NAMESPACE_FIELD = "namespace";
+    static final String SCHEMA_FIELD = "schema";
+    static final String PROPERTY_FIELD = "property";
     static final String MAXIMUM_RESULT_COUNT_FIELD = "maximumResultCount";
     static final String RANKING_STRATEGY_FIELD = "rankingStrategy";
     private final Bundle mBundle;
@@ -136,10 +148,46 @@
         return mBundle.getInt(RANKING_STRATEGY_FIELD);
     }
 
+    /**
+     * Returns the list of schema to search the suggestion over.
+     *
+     * <p>If empty, will search over all schemas.
+     */
+    @NonNull
+    public List<String> getFilterSchemas() {
+        List<String> schemaTypes = mBundle.getStringArrayList(SCHEMA_FIELD);
+        if (schemaTypes == null) {
+            return Collections.emptyList();
+        }
+        return Collections.unmodifiableList(schemaTypes);
+    }
+
+    /**
+     * Returns the map of schema and target properties to search over.
+     *
+     * <p>If empty, will search over all schema and properties.
+     *
+     * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned
+     * by this function, rather than calling it multiple times.
+     */
+    @NonNull
+    public Map<String, List<String>> getFilterProperties() {
+        Bundle typePropertyPathsBundle = Preconditions.checkNotNull(
+                mBundle.getBundle(PROPERTY_FIELD));
+        Set<String> schemas = typePropertyPathsBundle.keySet();
+        Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size());
+        for (String schema : schemas) {
+            typePropertyPathsMap.put(schema, Preconditions.checkNotNull(
+                    typePropertyPathsBundle.getStringArrayList(schema)));
+        }
+        return typePropertyPathsMap;
+    }
 
     /** Builder for {@link SearchSuggestionSpec objects}. */
     public static final class Builder {
         private ArrayList<String> mNamespaces = new ArrayList<>();
+        private ArrayList<String> mSchemas = new ArrayList<>();
+        private Bundle mTypePropertyFilters = new Bundle();
         private final int mTotalResultCount;
         private @SuggestionRankingStrategy int mRankingStrategy =
                 SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT;
@@ -199,11 +247,220 @@
             return this;
         }
 
+        /**
+         * Adds a schema filter to {@link SearchSuggestionSpec} Entry. Only search for
+         * suggestions that has documents under the specified schema.
+         *
+         * <p>If unset, the query will search over all schema.
+         */
+        @NonNull
+        public Builder addFilterSchemas(@NonNull String... schemaTypes) {
+            Preconditions.checkNotNull(schemaTypes);
+            resetIfBuilt();
+            return addFilterSchemas(Arrays.asList(schemaTypes));
+        }
+
+        /**
+         * Adds a schema filter to {@link SearchSuggestionSpec} Entry. Only search for
+         * suggestions that has documents under the specified schema.
+         *
+         * <p>If unset, the query will search over all schema.
+         */
+        @NonNull
+        public Builder addFilterSchemas(@NonNull Collection<String> schemaTypes) {
+            Preconditions.checkNotNull(schemaTypes);
+            resetIfBuilt();
+            mSchemas.addAll(schemaTypes);
+            return this;
+        }
+
+// @exportToFramework:startStrip()
+
+        /**
+         * Adds a schema filter to {@link SearchSuggestionSpec} Entry. Only search for
+         * suggestions that has documents under the specified schema.
+         *
+         * <p>If unset, the query will search over all schema.
+         *
+         * @param documentClasses classes annotated with {@link Document}.
+         */
+        // Merged list available from getFilterSchemas()
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder addFilterDocumentClasses(@NonNull Class<?>... documentClasses)
+                throws AppSearchException {
+            Preconditions.checkNotNull(documentClasses);
+            resetIfBuilt();
+            return addFilterDocumentClasses(Arrays.asList(documentClasses));
+        }
+// @exportToFramework:endStrip()
+
+// @exportToFramework:startStrip()
+
+        /**
+         * Adds the Schema names of given document classes to the Schema type filter of
+         * {@link SearchSuggestionSpec} Entry. Only search for suggestions that has documents
+         * under the specified schema.
+         *
+         * <p>If unset, the query will search over all schema.
+         *
+         * @param documentClasses classes annotated with {@link Document}.
+         */
+        // Merged list available from getFilterSchemas
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder addFilterDocumentClasses(
+                @NonNull Collection<? extends Class<?>> documentClasses) throws AppSearchException {
+            Preconditions.checkNotNull(documentClasses);
+            resetIfBuilt();
+            List<String> schemas = new ArrayList<>(documentClasses.size());
+            DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
+            for (Class<?> documentClass : documentClasses) {
+                DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
+                schemas.add(factory.getSchemaName());
+            }
+            addFilterSchemas(schemas);
+            return this;
+        }
+// @exportToFramework:endStrip()
+
+        /**
+         * Adds property paths for the specified type to the property filter of
+         * {@link SearchSuggestionSpec} Entry. Only search for suggestions that has content under
+         * the specified property. If property paths are added for a type, then only the
+         * properties referred to will be retrieved for results of that type.
+         *
+         * <p> If a property path that is specified isn't present in a result, it will be ignored
+         * for that result. Property paths cannot be null.
+         *
+         * <p>If no property paths are added for a particular type, then all properties of
+         * results of that type will be retrieved.
+         *
+         * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc.
+         *
+         * @param schema the {@link AppSearchSchema} that contains the target properties
+         * @param propertyPaths The String version of {@link PropertyPath}. A dot-delimited
+         *                      sequence of property names indicating which property in the
+         *                      document these snippets correspond to.
+         */
+        @NonNull
+        public Builder addFilterProperties(@NonNull String schema,
+                @NonNull Collection<String> propertyPaths) {
+            Preconditions.checkNotNull(schema);
+            Preconditions.checkNotNull(propertyPaths);
+            resetIfBuilt();
+            ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
+            for (String propertyPath : propertyPaths) {
+                Preconditions.checkNotNull(propertyPath);
+                propertyPathsArrayList.add(propertyPath);
+            }
+            mTypePropertyFilters.putStringArrayList(schema, propertyPathsArrayList);
+            return this;
+        }
+
+        /**
+         * Adds property paths for the specified type to the property filter of
+         * {@link SearchSuggestionSpec} Entry. Only search for suggestions that has content under
+         * the specified property. If property paths are added for a type, then only the
+         * properties referred to will be retrieved for results of that type.
+         *
+         * <p> If a property path that is specified isn't present in a result, it will be ignored
+         * for that result. Property paths cannot be null.
+         *
+         * <p>If no property paths are added for a particular type, then all properties of
+         * results of that type will be retrieved.
+         *
+         * @param schema the {@link AppSearchSchema} that contains the target properties
+         * @param propertyPaths The {@link PropertyPath} to search suggestion over
+         */
+        @NonNull
+        public Builder addFilterPropertyPaths(@NonNull String schema,
+                @NonNull Collection<PropertyPath> propertyPaths) {
+            Preconditions.checkNotNull(schema);
+            Preconditions.checkNotNull(propertyPaths);
+            ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
+            for (PropertyPath propertyPath : propertyPaths) {
+                propertyPathsArrayList.add(propertyPath.toString());
+            }
+            return addFilterProperties(schema, propertyPathsArrayList);
+        }
+
+
+// @exportToFramework:startStrip()
+        /**
+         * Adds property paths for the specified type to the property filter of
+         * {@link SearchSuggestionSpec} Entry. Only search for suggestions that has content under
+         * the specified property. If property paths are added for a type, then only the
+         * properties referred to will be retrieved for results of that type.
+         *
+         * <p> If a property path that is specified isn't present in a result, it will be ignored
+         * for that result. Property paths cannot be null.
+         *
+         * <p>If no property paths are added for a particular type, then all properties of
+         * results of that type will be retrieved.
+         *
+         * @param documentClass class annotated with {@link Document}.
+         * @param propertyPaths The String version of {@link PropertyPath}. A
+         * {@code dot-delimited sequence of property names indicating which property in the
+         * document these snippets correspond to.
+         */
+        @NonNull
+        public Builder addFilterProperties(@NonNull Class<?> documentClass,
+                @NonNull Collection<String> propertyPaths) throws AppSearchException {
+            Preconditions.checkNotNull(documentClass);
+            Preconditions.checkNotNull(propertyPaths);
+            resetIfBuilt();
+            DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
+            DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
+            return addFilterProperties(factory.getSchemaName(), propertyPaths);
+        }
+// @exportToFramework:endStrip()
+
+// @exportToFramework:startStrip()
+        /**
+         * Adds property paths for the specified type to the property filter of
+         * {@link SearchSuggestionSpec} Entry. Only search for suggestions that has content under
+         * the specified property. If property paths are added for a type, then only the
+         * properties referred to will be retrieved for results of that type.
+         *
+         * <p> If a property path that is specified isn't present in a result, it will be ignored
+         * for that result. Property paths cannot be null.
+         *
+         * <p>If no property paths are added for a particular type, then all properties of
+         * results of that type will be retrieved.
+         *
+         * @param documentClass class annotated with {@link Document}.
+         * @param propertyPaths The {@link PropertyPath} to search suggestion over
+         */
+        @NonNull
+        public Builder addFilterPropertyPaths(@NonNull Class<?> documentClass,
+                @NonNull Collection<PropertyPath> propertyPaths) throws AppSearchException {
+            Preconditions.checkNotNull(documentClass);
+            Preconditions.checkNotNull(propertyPaths);
+            resetIfBuilt();
+            DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
+            DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
+            return addFilterPropertyPaths(factory.getSchemaName(), propertyPaths);
+        }
+// @exportToFramework:endStrip()
+
         /** Constructs a new {@link SearchSpec} from the contents of this builder. */
         @NonNull
-        public SearchSuggestionSpec build() {
+        public SearchSuggestionSpec build() throws AppSearchException {
             Bundle bundle = new Bundle();
+            Set<String> schemaFilter = new ArraySet<>(mSchemas);
+            if (!mSchemas.isEmpty()) {
+                for (String schema : mTypePropertyFilters.keySet()) {
+                    if (!schemaFilter.contains(schema)) {
+                        throw new AppSearchException(RESULT_INVALID_ARGUMENT,
+                                "The schema: " + schema + " exists in the property filter but "
+                                        + "doesn't exist in the schema filter.");
+                    }
+                }
+            }
             bundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
+            bundle.putStringArrayList(SCHEMA_FIELD, mSchemas);
+            bundle.putBundle(PROPERTY_FIELD, mTypePropertyFilters);
             bundle.putInt(MAXIMUM_RESULT_COUNT_FIELD, mTotalResultCount);
             bundle.putInt(RANKING_STRATEGY_FIELD, mRankingStrategy);
             mBuilt = true;
@@ -213,6 +470,8 @@
         private void resetIfBuilt() {
             if (mBuilt) {
                 mNamespaces = new ArrayList<>(mNamespaces);
+                mSchemas = new ArrayList<>(mSchemas);
+                mTypePropertyFilters = BundleUtil.deepCopy(mTypePropertyFilters);
                 mBuilt = false;
             }
         }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
index 96b0f88..c23a469 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
@@ -197,6 +197,9 @@
             } else if (tokenizerType == 1) {  // TOKENIZER_TYPE_PLAIN
                 tokenizerEnum = mHelper.getAppSearchClass(
                         "AppSearchSchema", "StringPropertyConfig", "TOKENIZER_TYPE_PLAIN");
+            } else if (tokenizerType == 3) { // TOKENIZER_TYPE_RFC822
+                tokenizerEnum = mHelper.getAppSearchClass(
+                        "AppSearchSchema", "StringPropertyConfig", "TOKENIZER_TYPE_RFC822");
             } else {
                 throw new ProcessingException("Unknown tokenizer type " + tokenizerType, property);
             }
diff --git a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
index 1339501..c461fc3 100644
--- a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
+++ b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
@@ -937,8 +937,30 @@
                         + "public class Gift {\n"
                         + "  @Document.Namespace String namespace;\n"
                         + "  @Document.Id String id;\n"
-                        + "  @Document.StringProperty(tokenizerType=0) String tokNone;\n"
-                        + "  @Document.StringProperty(tokenizerType=1) String tokPlain;\n"
+                        + "\n"
+                        // NONE index type will generate a NONE tokenizerType type.
+                        + "  @Document.StringProperty(tokenizerType=0, indexingType=0) "
+                        + "  String tokNoneInvalid;\n"
+                        + "  @Document.StringProperty(tokenizerType=1, indexingType=0) "
+                        + "  String tokPlainInvalid;\n"
+                        + "  @Document.StringProperty(tokenizerType=3, indexingType=0) "
+                        + "  String tokRfc822Invalid;\n"
+                        + "\n"
+                        // Indexing type exact.
+                        + "  @Document.StringProperty(tokenizerType=0, indexingType=1) "
+                        + "  String tokNone;\n"
+                        + "  @Document.StringProperty(tokenizerType=1, indexingType=1) "
+                        + "  String tokPlain;\n"
+                        + "  @Document.StringProperty(tokenizerType=3, indexingType=1) "
+                        + "  String tokRfc822;\n"
+                        + "\n"
+                        // Indexing type prefix.
+                        + "  @Document.StringProperty(tokenizerType=0, indexingType=2) "
+                        + "  String tokNonePrefix;\n"
+                        + "  @Document.StringProperty(tokenizerType=1, indexingType=2) "
+                        + "  String tokPlainPrefix;\n"
+                        + "  @Document.StringProperty(tokenizerType=3, indexingType=2) "
+                        + "  String tokRfc822Prefix;\n"
                         + "}\n");
 
         assertThat(compilation).succeededWithoutWarnings();
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
index b6ccd81..0fa23d4 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testTokenizerType.JAVA
@@ -20,16 +20,51 @@
   @Override
   public AppSearchSchema getSchema() throws AppSearchException {
     return new AppSearchSchema.Builder(SCHEMA_NAME)
-          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokNone")
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokNoneInvalid")
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
             .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
             .build())
-          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokPlain")
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokPlainInvalid")
             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
             .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
             .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
             .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokRfc822Invalid")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokNone")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokPlain")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokRfc822")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_RFC822)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokNonePrefix")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokPlainPrefix")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("tokRfc822Prefix")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_RFC822)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+            .build())
           .build();
   }
 
@@ -37,6 +72,18 @@
   public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
     GenericDocument.Builder<?> builder =
         new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String tokNoneInvalidCopy = document.tokNoneInvalid;
+    if (tokNoneInvalidCopy != null) {
+      builder.setPropertyString("tokNoneInvalid", tokNoneInvalidCopy);
+    }
+    String tokPlainInvalidCopy = document.tokPlainInvalid;
+    if (tokPlainInvalidCopy != null) {
+      builder.setPropertyString("tokPlainInvalid", tokPlainInvalidCopy);
+    }
+    String tokRfc822InvalidCopy = document.tokRfc822Invalid;
+    if (tokRfc822InvalidCopy != null) {
+      builder.setPropertyString("tokRfc822Invalid", tokRfc822InvalidCopy);
+    }
     String tokNoneCopy = document.tokNone;
     if (tokNoneCopy != null) {
       builder.setPropertyString("tokNone", tokNoneCopy);
@@ -45,6 +92,22 @@
     if (tokPlainCopy != null) {
       builder.setPropertyString("tokPlain", tokPlainCopy);
     }
+    String tokRfc822Copy = document.tokRfc822;
+    if (tokRfc822Copy != null) {
+      builder.setPropertyString("tokRfc822", tokRfc822Copy);
+    }
+    String tokNonePrefixCopy = document.tokNonePrefix;
+    if (tokNonePrefixCopy != null) {
+      builder.setPropertyString("tokNonePrefix", tokNonePrefixCopy);
+    }
+    String tokPlainPrefixCopy = document.tokPlainPrefix;
+    if (tokPlainPrefixCopy != null) {
+      builder.setPropertyString("tokPlainPrefix", tokPlainPrefixCopy);
+    }
+    String tokRfc822PrefixCopy = document.tokRfc822Prefix;
+    if (tokRfc822PrefixCopy != null) {
+      builder.setPropertyString("tokRfc822Prefix", tokRfc822PrefixCopy);
+    }
     return builder.build();
   }
 
@@ -52,6 +115,21 @@
   public Gift fromGenericDocument(GenericDocument genericDoc) throws AppSearchException {
     String idConv = genericDoc.getId();
     String namespaceConv = genericDoc.getNamespace();
+    String[] tokNoneInvalidCopy = genericDoc.getPropertyStringArray("tokNoneInvalid");
+    String tokNoneInvalidConv = null;
+    if (tokNoneInvalidCopy != null && tokNoneInvalidCopy.length != 0) {
+      tokNoneInvalidConv = tokNoneInvalidCopy[0];
+    }
+    String[] tokPlainInvalidCopy = genericDoc.getPropertyStringArray("tokPlainInvalid");
+    String tokPlainInvalidConv = null;
+    if (tokPlainInvalidCopy != null && tokPlainInvalidCopy.length != 0) {
+      tokPlainInvalidConv = tokPlainInvalidCopy[0];
+    }
+    String[] tokRfc822InvalidCopy = genericDoc.getPropertyStringArray("tokRfc822Invalid");
+    String tokRfc822InvalidConv = null;
+    if (tokRfc822InvalidCopy != null && tokRfc822InvalidCopy.length != 0) {
+      tokRfc822InvalidConv = tokRfc822InvalidCopy[0];
+    }
     String[] tokNoneCopy = genericDoc.getPropertyStringArray("tokNone");
     String tokNoneConv = null;
     if (tokNoneCopy != null && tokNoneCopy.length != 0) {
@@ -62,11 +140,38 @@
     if (tokPlainCopy != null && tokPlainCopy.length != 0) {
       tokPlainConv = tokPlainCopy[0];
     }
+    String[] tokRfc822Copy = genericDoc.getPropertyStringArray("tokRfc822");
+    String tokRfc822Conv = null;
+    if (tokRfc822Copy != null && tokRfc822Copy.length != 0) {
+      tokRfc822Conv = tokRfc822Copy[0];
+    }
+    String[] tokNonePrefixCopy = genericDoc.getPropertyStringArray("tokNonePrefix");
+    String tokNonePrefixConv = null;
+    if (tokNonePrefixCopy != null && tokNonePrefixCopy.length != 0) {
+      tokNonePrefixConv = tokNonePrefixCopy[0];
+    }
+    String[] tokPlainPrefixCopy = genericDoc.getPropertyStringArray("tokPlainPrefix");
+    String tokPlainPrefixConv = null;
+    if (tokPlainPrefixCopy != null && tokPlainPrefixCopy.length != 0) {
+      tokPlainPrefixConv = tokPlainPrefixCopy[0];
+    }
+    String[] tokRfc822PrefixCopy = genericDoc.getPropertyStringArray("tokRfc822Prefix");
+    String tokRfc822PrefixConv = null;
+    if (tokRfc822PrefixCopy != null && tokRfc822PrefixCopy.length != 0) {
+      tokRfc822PrefixConv = tokRfc822PrefixCopy[0];
+    }
     Gift document = new Gift();
     document.namespace = namespaceConv;
     document.id = idConv;
+    document.tokNoneInvalid = tokNoneInvalidConv;
+    document.tokPlainInvalid = tokPlainInvalidConv;
+    document.tokRfc822Invalid = tokRfc822InvalidConv;
     document.tokNone = tokNoneConv;
     document.tokPlain = tokPlainConv;
+    document.tokRfc822 = tokRfc822Conv;
+    document.tokNonePrefix = tokNonePrefixConv;
+    document.tokPlainPrefix = tokPlainPrefixConv;
+    document.tokRfc822Prefix = tokRfc822PrefixConv;
     return document;
   }
 }
diff --git a/arch/core/core-common/api/restricted_current.txt b/arch/core/core-common/api/restricted_current.txt
index 7f1c354..4fbc435 100644
--- a/arch/core/core-common/api/restricted_current.txt
+++ b/arch/core/core-common/api/restricted_current.txt
@@ -20,11 +20,15 @@
     method public int size();
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SafeIterableMap.IteratorWithAdditions implements java.util.Iterator<java.util.Map.Entry<K,V>> {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SafeIterableMap.IteratorWithAdditions extends androidx.arch.core.internal.SafeIterableMap.SupportRemove<K,V> implements java.util.Iterator<java.util.Map.Entry<K,V>> {
     method public boolean hasNext();
     method public java.util.Map.Entry<K!,V!>! next();
   }
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract static class SafeIterableMap.SupportRemove<K, V> {
+    ctor public SafeIterableMap.SupportRemove();
+  }
+
 }
 
 package androidx.arch.core.util {
diff --git a/arch/core/core-common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java b/arch/core/core-common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java
index 08cce2f..bdaeca8 100644
--- a/arch/core/core-common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java
+++ b/arch/core/core-common/src/main/java/androidx/arch/core/internal/SafeIterableMap.java
@@ -358,7 +358,14 @@
         }
     }
 
-    abstract static class SupportRemove<K, V> {
+    /**
+     * @hide
+     *
+     * @param <K>
+     * @param <V>
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+    public abstract static class SupportRemove<K, V> {
         abstract void supportRemove(@NonNull Entry<K, V> entry);
     }
 
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellBehaviorTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellBehaviorTest.kt
index bd01556..493611c 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellBehaviorTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellBehaviorTest.kt
@@ -62,12 +62,16 @@
     @Test
     fun pidof() {
         // Should only be one process - this one!
-        val pidofString = Shell.executeScriptCaptureStdout("pidof ${Packages.TEST}").trim()
+        val output = Shell.executeScriptCaptureStdoutStderr("pidof ${Packages.TEST}")
+        val pidofString = output.stdout.trim()
 
         when {
             Build.VERSION.SDK_INT < 23 -> {
-                // command doesn't exist (and we don't try and read stderr here)
-                assertEquals("", pidofString)
+                // command doesn't exist
+                assertTrue(
+                    output.stdout.isBlank() && output.stderr.isNotBlank(),
+                    "saw output $output"
+                )
             }
             Build.VERSION.SDK_INT == 23 -> {
                 // on API 23 specifically, pidof prints... all processes, ignoring the arg...
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellTest.kt
index c23a119..576a03b 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellTest.kt
@@ -68,10 +68,7 @@
         val output = Shell.optionalCommand("echo foo")
 
         val expected = when {
-            Build.VERSION.SDK_INT >= 23 -> "foo\n"
-            // known bug in the shell on L (21,22). `echo` doesn't work with shell
-            // programmatically, only works in interactive shell :|
-            Build.VERSION.SDK_INT in 21..22 -> ""
+            Build.VERSION.SDK_INT >= 21 -> "foo\n"
             else -> null
         }
 
@@ -153,7 +150,7 @@
         )
     }
 
-    @SdkSuppress(minSdkVersion = 26) // xargs only available 26+
+    @SdkSuppress(minSdkVersion = 23) // xargs added api 23
     @Test
     fun executeScriptCaptureStdout_pipe_xargs() {
         // validate piping works with xargs
@@ -170,7 +167,7 @@
         )
     }
 
-    @SdkSuppress(minSdkVersion = 26) // xargs only available 26+
+    @SdkSuppress(minSdkVersion = 23) // xargs added api 23
     @Test
     fun executeScriptCaptureStdout_stdinArg_xargs() {
         // validate stdin to first command in script
@@ -204,7 +201,7 @@
         )
     }
 
-    @SdkSuppress(minSdkVersion = 26) // xargs only available 26+
+    @SdkSuppress(minSdkVersion = 23) // xargs added api 23
     @Test
     fun executeScriptCaptureStdout_multilineRedirectStdin_xargs() {
         Assert.assertEquals(
@@ -383,7 +380,7 @@
         }
     }
 
-    @SdkSuppress(minSdkVersion = 21)
+    @SdkSuppress(minSdkVersion = 23) // xargs added api 23
     @Test
     fun shellReuse() {
         val script = Shell.createShellScript("xargs echo $1", stdin = "foo")
@@ -397,6 +394,32 @@
         script.cleanUp()
     }
 
+    @SdkSuppress(minSdkVersion = 21)
+    @Test
+    fun getChecksum() {
+        val emptyPaths = listOf("/data/local/tmp/emptyfile1", "/data/local/tmp/emptyfile2")
+        try {
+            val checksums = emptyPaths.map {
+                Shell.executeScriptSilent("rm -f $it")
+                Shell.executeScriptSilent("touch $it")
+                Shell.getChecksum(it)
+            }
+
+            assertEquals(checksums.first(), checksums.last())
+            if (Build.VERSION.SDK_INT < 23) {
+                checksums.forEach { checksum ->
+                    // getChecksum uses ls -l to check size pre API 23,
+                    // this validates that behavior + result parsing
+                    assertEquals("0", checksum)
+                }
+            }
+        } finally {
+            emptyPaths.forEach {
+                Shell.executeScriptSilent("rm -f $it")
+            }
+        }
+    }
+
     @RequiresApi(21)
     private fun pidof(packageName: String): Int? {
         return Shell.getPidsForProcess(packageName).firstOrNull()
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 86d304e..0f0b95f 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
@@ -107,27 +107,63 @@
             }
     }
 
+    /**
+     * Get a checksum for a given path
+     *
+     * Note: Does not check for stderr, as this method is used during ShellImpl init, so stderr not
+     * yet available
+     */
     @RequiresApi(21)
-    fun chmodExecutable(absoluteFilePath: String) {
-        // use unsafe commands, as this is used in Shell.executeScript
+    internal fun getChecksum(path: String): String {
+        val sum = if (Build.VERSION.SDK_INT >= 23) {
+            ShellImpl.executeCommandUnsafe("md5sum $path").substringBefore(" ")
+        } else {
+            // this isn't good, but it's good enough for API 22
+            val out = ShellImpl.executeCommandUnsafe("ls -l $path").split(Regex("\\s+"))[3]
+            println("value is $out")
+            out
+        }
+        check(sum.isNotBlank()) {
+            "Checksum for $path was blank"
+        }
+        return sum
+    }
+
+    /**
+     * Copy file and make executable
+     *
+     * Note: this operation does checksum validation of dst, since it's used during setup of the
+     * shell script used to capture stderr, so stderr isn't available.
+     */
+    @RequiresApi(21)
+    private fun moveToTmpAndMakeExecutable(src: String, dst: String) {
+        ShellImpl.executeCommandUnsafe("cp $src $dst")
         if (Build.VERSION.SDK_INT >= 23) {
-            ShellImpl.executeCommandUnsafe("chmod +x $absoluteFilePath")
+            ShellImpl.executeCommandUnsafe("chmod +x $dst")
         } else {
             // chmod with support for +x only added in API 23
             // While 777 is technically more permissive, this is only used for scripts and temporary
             // files in tests, so we don't worry about permissions / access here
-            ShellImpl.executeCommandUnsafe("chmod 777 $absoluteFilePath")
+            ShellImpl.executeCommandUnsafe("chmod 777 $dst")
         }
-    }
 
-    @RequiresApi(21)
-    fun moveToTmpAndMakeExecutable(src: String, dst: String) {
-        ShellImpl.executeCommandUnsafe("cp $src $dst")
-        chmodExecutable(dst)
+        // validate checksums instead of checking stderr, since it's not yet safe to
+        // read from stderr. This detects the problem where root left a stale executable
+        // that can't be modified by shell at the dst path
+        val srcSum = getChecksum(src)
+        val dstSum = getChecksum(dst)
+        if (srcSum != dstSum) {
+            throw IllegalStateException("Failed to verify copied executable $dst, " +
+                "md5 sums $srcSum, $dstSum don't match. Check if root owns" +
+                " $dst and if so, delete it with `adb root`-ed shell session.")
+        }
     }
 
     /**
      * Writes the inputStream to an executable file with the given name in `/data/local/tmp`
+     *
+     * Note: this operation does not validate command success, since it's used during setup of shell
+     * scripting code used to parse stderr. This means callers should validate.
      */
     @RequiresApi(21)
     fun createRunnableExecutable(name: String, inputStream: InputStream): String {
@@ -440,6 +476,15 @@
     fun pathExists(absoluteFilePath: String): Boolean {
         return ShellImpl.executeCommandUnsafe("ls $absoluteFilePath").trim() == absoluteFilePath
     }
+
+    @RequiresApi(21)
+    fun amBroadcast(broadcastArguments: String): Int? {
+        // unsafe here for perf, since we validate the return value so we don't need to check stderr
+        return ShellImpl.executeCommandUnsafe("am broadcast $broadcastArguments")
+            .substringAfter("Broadcast completed: result=")
+            .trim()
+            .toIntOrNull()
+    }
 }
 
 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@@ -467,7 +512,8 @@
         // These variables are used in executeCommand and executeScript, so we keep them as var
         // instead of val and use a separate initializer
         isSessionRooted = executeCommandUnsafe("id").contains("uid=0(root)")
-        // use a script below, since `su` command failure is unrecoverable on some API levels
+        // use a script below, since direct `su` command failure brings down this process
+        // on some API levels (and can fail even on userdebug builds)
         isSuAvailable = createShellScript(
             script = "su root id",
             stdin = null
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 1b8fa29..01381a9 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
@@ -26,9 +26,9 @@
 import androidx.benchmark.userspaceTrace
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.tracing.trace
-import org.jetbrains.annotations.TestOnly
 import java.io.File
 import java.io.IOException
+import org.jetbrains.annotations.TestOnly
 
 /**
  * PerfettoHelper is used to start and stop the perfetto tracing and move the
@@ -122,8 +122,10 @@
             // Perfetto
             val perfettoCmd = perfettoCommand(actualConfigPath, isTextProtoConfig)
             Log.i(LOG_TAG, "Starting perfetto tracing with cmd: $perfettoCmd")
-            val perfettoCmdOutput =
-                Shell.executeScriptCaptureStdout("$perfettoCmd; echo EXITCODE=$?").trim()
+            // Note: we intentionally don't check stderr, as benign warnings are printed
+            val perfettoCmdOutput = Shell.executeScriptCaptureStdoutStderr(
+                "$perfettoCmd; echo EXITCODE=$?"
+            ).stdout.trim()
 
             val expectedSuffix = "\nEXITCODE=0"
             if (!perfettoCmdOutput.endsWith(expectedSuffix)) {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/UiState.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/UiState.kt
index bcd94d2..c9949cc 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/UiState.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/UiState.kt
@@ -16,16 +16,19 @@
 
 package androidx.benchmark.perfetto
 
+import android.os.Build
+import android.util.Log
 import androidx.annotation.RestrictTo
+import androidx.benchmark.BenchmarkState.Companion.TAG
+import java.io.File
+import java.io.FileNotFoundException
 import perfetto.protos.Trace
 import perfetto.protos.TracePacket
 import perfetto.protos.UiState
-import java.io.File
 
 /**
  * Convenience for UiState construction with specified package
  */
-@Suppress("FunctionName") // constructor convenience
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 fun UiState(
     timelineStart: Long?,
@@ -42,5 +45,19 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 fun File.appendUiState(state: UiState) {
     val traceToAppend = Trace(packet = listOf(TracePacket(ui_state = state)))
-    appendBytes(traceToAppend.encode())
+    appendBytesSafely(traceToAppend.encode())
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun File.appendBytesSafely(bytes: ByteArray) {
+    try {
+        appendBytes(bytes)
+    } catch (e: FileNotFoundException) {
+        if (Build.VERSION.SDK_INT in 21..22) {
+            // Failure is common on API 21/22 due to b/227510293
+            Log.d(TAG, "Unable to append additional bytes to ${this.absolutePath}")
+        } else {
+            throw e
+        }
+    }
 }
diff --git a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
index 676ce86..1671a9b 100644
--- a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
+++ b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/BenchmarkRule.kt
@@ -24,13 +24,13 @@
 import androidx.benchmark.UserspaceTracing
 import androidx.benchmark.perfetto.PerfettoCaptureWrapper
 import androidx.benchmark.perfetto.UiState
+import androidx.benchmark.perfetto.appendBytesSafely
 import androidx.benchmark.perfetto.appendUiState
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.GrantPermissionRule
 import androidx.tracing.Trace
 import androidx.tracing.trace
 import java.io.File
-import java.io.FileNotFoundException
 import org.junit.Assert.assertTrue
 import org.junit.Assume.assumeTrue
 import org.junit.rules.RuleChain
@@ -225,23 +225,16 @@
                 userspaceTrace = UserspaceTracing.commitToTrace()
             }?.apply {
                 // trace completed, and copied into app writeable dir
-
-                try {
-                    val file = File(this)
-
-                    file.appendBytes(userspaceTrace!!.encode())
-                    file.appendUiState(
-                        UiState(
-                            timelineStart = null,
-                            timelineEnd = null,
-                            highlightPackage = InstrumentationRegistry.getInstrumentation()
-                                .context.packageName
-                        )
+                val file = File(this)
+                file.appendBytesSafely(userspaceTrace!!.encode())
+                file.appendUiState(
+                    UiState(
+                        timelineStart = null,
+                        timelineEnd = null,
+                        highlightPackage = InstrumentationRegistry.getInstrumentation()
+                            .context.packageName
                     )
-                } catch (exception: FileNotFoundException) {
-                    // TODO(b/227510293): fix record to return a null in this case
-                    Log.d(TAG, "Unable to add additional detail to captured trace $this")
-                }
+                )
             }
 
             if (enableReport) {
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/ProfileInstallBroadcastTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/ProfileInstallBroadcastTest.kt
index cf9a3e3..055ccfd 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/ProfileInstallBroadcastTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/ProfileInstallBroadcastTest.kt
@@ -17,16 +17,24 @@
 package androidx.benchmark.macro
 
 import android.os.Build
+import androidx.benchmark.junit4.PerfettoTraceRule
+import androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import kotlin.test.assertNull
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 class ProfileInstallBroadcastTest {
+    @OptIn(ExperimentalPerfettoCaptureApi::class)
+    @get:Rule
+    val perfettoTraceRule = PerfettoTraceRule()
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
     @Test
     fun installProfile() {
         assertNull(ProfileInstallBroadcast.installProfile(Packages.TARGET))
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt
index ff6b62a..088d93a 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt
@@ -22,6 +22,7 @@
 import androidx.benchmark.perfetto.PerfettoHelper
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.LOWEST_BUNDLED_VERSION_SUPPORTED
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.isAbiSupported
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.testutils.verifyWithPolling
@@ -60,10 +61,12 @@
         PerfettoHelper.stopAllPerfettoProcesses()
     }
 
+    @FlakyTest(bugId = 258216025)
     @SdkSuppress(minSdkVersion = LOWEST_BUNDLED_VERSION_SUPPORTED)
     @Test
     fun captureAndValidateTrace_bundled() = captureAndValidateTrace(unbundled = false)
 
+    @FlakyTest(bugId = 258216025)
     @Test
     fun captureAndValidateTrace_unbundled() = captureAndValidateTrace(unbundled = true)
 
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 7023a69..7583d02 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
@@ -21,11 +21,11 @@
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.isAbiSupported
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import java.util.Locale
+import kotlin.test.assertEquals
 import org.junit.Assume.assumeTrue
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.util.Locale
-import kotlin.test.assertEquals
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/ProfileInstallBroadcast.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/ProfileInstallBroadcast.kt
index dc8d53f..f81127b 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/ProfileInstallBroadcast.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/ProfileInstallBroadcast.kt
@@ -16,6 +16,7 @@
 
 package androidx.benchmark.macro
 
+import android.os.Build
 import android.util.Log
 import androidx.annotation.RequiresApi
 import androidx.benchmark.Shell
@@ -26,7 +27,8 @@
     private val receiverName = ProfileInstallReceiver::class.java.name
 
     /**
-     * Returns null on success, or an error string otherwise.
+     * Returns null on success, error string on suppress-able error, or throws if profileinstaller
+     * not up to date.
      *
      * Returned error strings aren't thrown, to let the calling function decide strictness.
      */
@@ -36,13 +38,7 @@
         // installed synchronously
         val action = ProfileInstallReceiver.ACTION_INSTALL_PROFILE
         // Use an explicit broadcast given the app was force-stopped.
-        val result = Shell.executeScriptCaptureStdout(
-            "am broadcast -a $action $packageName/$receiverName"
-        )
-            .substringAfter("Broadcast completed: result=")
-            .trim()
-            .toIntOrNull()
-        when (result) {
+        when (val result = Shell.amBroadcast("-a $action $packageName/$receiverName")) {
             null,
                 // 0 is returned by the platform by default, and also if no broadcast receiver
                 // receives the broadcast.
@@ -63,12 +59,25 @@
                 )
             }
             ProfileInstaller.RESULT_UNSUPPORTED_ART_VERSION -> {
+                val sdkInt = Build.VERSION.SDK_INT
                 throw RuntimeException(
-                    "Baseline profiles aren't supported on this device version"
+                    if (sdkInt <= 23) {
+                        "Baseline profiles aren't supported on this device version," +
+                            " as all apps are fully ahead-of-time compiled."
+                    } else {
+                        "The device SDK version ($sdkInt) isn't supported" +
+                            " by the target app's copy of profileinstaller." +
+                            if (sdkInt in 31..33) {
+                                " Please use profileinstaller `1.2.1`" +
+                                    " or newer for API 31-33 support"
+                            } else {
+                                ""
+                            }
+                    }
                 )
             }
             ProfileInstaller.RESULT_BASELINE_PROFILE_NOT_FOUND -> {
-                return "No baseline profile was found in the target apk."
+                    return "No baseline profile was found in the target apk."
             }
             ProfileInstaller.RESULT_NOT_WRITABLE,
             ProfileInstaller.RESULT_DESIRED_FORMAT_UNSUPPORTED,
@@ -103,12 +112,7 @@
         val action = "androidx.profileinstaller.action.SKIP_FILE"
         val operationKey = "EXTRA_SKIP_FILE_OPERATION"
         val extras = "$operationKey $operation"
-        val result = Shell.executeScriptCaptureStdout(
-            "am broadcast -a $action -e $extras $packageName/$receiverName"
-        )
-            .substringAfter("Broadcast completed: result=")
-            .trim()
-            .toIntOrNull()
+        val result = Shell.amBroadcast("-a $action -e $extras $packageName/$receiverName")
         return when {
             result == null || result == 0 -> {
                 // 0 is returned by the platform by default, and also if no broadcast receiver
@@ -143,13 +147,7 @@
     fun saveProfile(packageName: String): String? {
         Log.d(TAG, "Profile Installer - Save Profile")
         val action = "androidx.profileinstaller.action.SAVE_PROFILE"
-        val result = Shell.executeScriptCaptureStdout(
-            "am broadcast -a $action $packageName/$receiverName"
-        )
-            .substringAfter("Broadcast completed: result=")
-            .trim()
-            .toIntOrNull()
-        return when (result) {
+        return when (val result = Shell.amBroadcast("-a $action $packageName/$receiverName")) {
             null, 0 -> {
                 // 0 is returned by the platform by default, and also if no broadcast receiver
                 // receives the broadcast.
@@ -176,20 +174,19 @@
         }
     }
 
-    private fun benchmarkOperation(packageName: String, operation: String): String? {
+    private fun benchmarkOperation(
+        packageName: String,
+        @Suppress("SameParameterValue") operation: String
+    ): String? {
         Log.d(TAG, "Profile Installer - Benchmark Operation: $operation")
         // Redefining constants here, because these are only defined in the latest alpha for
         // ProfileInstaller.
         // Use an explicit broadcast given the app was force-stopped.
         val action = "androidx.profileinstaller.action.BENCHMARK_OPERATION"
         val operationKey = "EXTRA_BENCHMARK_OPERATION"
-        val extras = "$operationKey $operation"
-        val result = Shell.executeScriptCaptureStdout(
-            "am broadcast -a $action -e $extras $packageName/$receiverName"
+        val result = Shell.amBroadcast(
+            "-a $action -e $operationKey $operation $packageName/$receiverName"
         )
-            .substringAfter("Broadcast completed: result=")
-            .trim()
-            .toIntOrNull()
         return when (result) {
             null, 0, 16 /* BENCHMARK_OPERATION_UNKNOWN */ -> {
                 // 0 is returned by the platform by default, and also if no broadcast receiver
diff --git a/biometric/biometric/src/main/res/values-en-rCA/strings.xml b/biometric/biometric/src/main/res/values-en-rCA/strings.xml
index 732ed1b..6ce1f33 100644
--- a/biometric/biometric/src/main/res/values-en-rCA/strings.xml
+++ b/biometric/biometric/src/main/res/values-en-rCA/strings.xml
@@ -18,17 +18,17 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Touch the fingerprint sensor"</string>
-    <string name="fingerprint_not_recognized" msgid="3873359464293253009">"Not recognised"</string>
+    <string name="fingerprint_not_recognized" msgid="3873359464293253009">"Not recognized"</string>
     <string name="fingerprint_error_hw_not_available" msgid="8216738333501875566">"Fingerprint hardware not available."</string>
     <string name="fingerprint_error_no_fingerprints" msgid="7520712796891883488">"No fingerprints enrolled."</string>
     <string name="fingerprint_error_hw_not_present" msgid="6306988885793029438">"This device does not have a fingerprint sensor"</string>
-    <string name="fingerprint_error_user_canceled" msgid="7627716295344353987">"Fingerprint operation cancelled by user."</string>
+    <string name="fingerprint_error_user_canceled" msgid="7627716295344353987">"Fingerprint operation canceled by user."</string>
     <string name="fingerprint_error_lockout" msgid="7291787166416782245">"Too many attempts. Please try again later."</string>
     <string name="default_error_msg" msgid="4776854077120974966">"Unknown error"</string>
-    <string name="generic_error_user_canceled" msgid="7309881387583143581">"Authentication cancelled by user."</string>
+    <string name="generic_error_user_canceled" msgid="7309881387583143581">"Authentication canceled by user."</string>
     <string name="confirm_device_credential_password" msgid="5912733858573823945">"Use password"</string>
-    <string name="generic_error_no_device_credential" msgid="3791785319221634505">"No PIN, pattern or password set."</string>
-    <string name="generic_error_no_keyguard" msgid="1807436368654974044">"This device does not support PIN, pattern or password."</string>
+    <string name="generic_error_no_device_credential" msgid="3791785319221634505">"No PIN, pattern, or password set."</string>
+    <string name="generic_error_no_keyguard" msgid="1807436368654974044">"This device does not support PIN, pattern, or password."</string>
     <string name="fingerprint_dialog_icon_description" msgid="5462024216548165325">"Fingerprint icon"</string>
     <string name="use_fingerprint_label" msgid="6961788485681412417">"Use fingerprint"</string>
     <string name="use_face_label" msgid="6533512708069459542">"Use face"</string>
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
index 01286f7..56668f8 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
@@ -136,6 +136,11 @@
  */
 const val XCODEGEN_DOWNLOAD_URI = "androidx.benchmark.darwin.xcodeGenDownloadUri"
 
+/**
+ * If true, don't restrict usage of compileSdk property.
+ */
+const val ALLOW_CUSTOM_COMPILE_SDK = "androidx.allowCustomCompileSdk"
+
 val ALL_ANDROIDX_PROPERTIES = setOf(
     ALTERNATIVE_PROJECT_URL,
     VERSION_EXTRA_CHECK_ENABLED,
@@ -162,7 +167,8 @@
     KMP_GITHUB_BUILD,
     ENABLED_KMP_TARGET_PLATFORMS,
     ALLOW_MISSING_LINT_CHECKS_PROJECT,
-    XCODEGEN_DOWNLOAD_URI
+    XCODEGEN_DOWNLOAD_URI,
+    ALLOW_CUSTOM_COMPILE_SDK
 )
 
 /**
@@ -250,4 +256,10 @@
 fun Project.allowMissingLintProject() =
     findBooleanProperty(ALLOW_MISSING_LINT_CHECKS_PROJECT) ?: false
 
+/**
+ * Whether libraries are allowed to customize the value of the compileSdk property.
+ */
+fun Project.isCustomCompileSdkAllowed(): Boolean =
+    findBooleanProperty(ALLOW_CUSTOM_COMPILE_SDK) ?: true
+
 fun Project.findBooleanProperty(propName: String) = (findProperty(propName) as? String)?.toBoolean()
\ No newline at end of file
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index c0a5ae5..91d7c75 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -608,7 +608,9 @@
             check(minSdkVersion >= DEFAULT_MIN_SDK_VERSION) {
                 "minSdkVersion $minSdkVersion lower than the default of $DEFAULT_MIN_SDK_VERSION"
             }
-            check(compileSdkVersion == COMPILE_SDK_VERSION) {
+            check(compileSdkVersion == COMPILE_SDK_VERSION ||
+                project.isCustomCompileSdkAllowed()
+            ) {
                 "compileSdkVersion must not be explicitly specified, was \"$compileSdkVersion\""
             }
             project.configurations.all { configuration ->
diff --git a/busytown/impl/build-metalava-and-androidx.sh b/busytown/impl/build-metalava-and-androidx.sh
index d24f86c..b609c1a 100755
--- a/busytown/impl/build-metalava-and-androidx.sh
+++ b/busytown/impl/build-metalava-and-androidx.sh
@@ -46,7 +46,7 @@
 
 # Mac grep doesn't support -P, so use perl version of `grep -oP "(?<=metalavaVersion=).*"`
 export METALAVA_VERSION=`perl -nle'print $& while m{(?<=metalavaVersion=).*}g' $METALAVA_DIR/src/main/resources/version.properties`
-export METALAVA_REPO="$CHECKOUT_ROOT/out/dist/repo/m2repository"
+export METALAVA_REPO="$DIST_DIR/repo/m2repository"
 
 function buildAndroidx() {
   ./frameworks/support/busytown/impl/build.sh $androidxArguments \
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
index 8f93913..657c698 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
@@ -28,6 +28,7 @@
 import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.Request
 import androidx.camera.camera2.pipe.Result3A
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
@@ -64,13 +65,14 @@
         cameraPipe,
     ),
 ) : UseCaseCamera {
-    val useCaseCameraGraphConfig = UseCaseCameraConfig(useCases).provideUseCaseGraphConfig(
-        callbackMap = callbackMap,
-        cameraConfig = cameraConfig,
-        cameraPipe = cameraPipe,
-        requestListener = ComboRequestListener(),
-        useCaseSurfaceManager = useCaseSurfaceManager,
-    )
+    val useCaseCameraGraphConfig =
+        UseCaseCameraConfig(useCases, CameraStateAdapter()).provideUseCaseGraphConfig(
+            callbackMap = callbackMap,
+            cameraConfig = cameraConfig,
+            cameraPipe = cameraPipe,
+            requestListener = ComboRequestListener(),
+            useCaseSurfaceManager = useCaseSurfaceManager,
+        )
 
     override val requestControl: UseCaseCameraRequestControl = UseCaseCameraRequestControlImpl(
         configAdapter = CaptureConfigAdapter(
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
index c3b6ccd5f..91b9fe9 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
@@ -67,7 +67,7 @@
 @OptIn(ExperimentalCoroutinesApi::class, ExperimentalCamera2Interop::class)
 class CameraControlAdapter @Inject constructor(
     private val cameraProperties: CameraProperties,
-    private val cameraStateAdapter: CameraStateAdapter,
+    private val cameraControlStateAdapter: CameraControlStateAdapter,
     private val evCompControl: EvCompControl,
     private val flashControl: FlashControl,
     private val torchControl: TorchControl,
@@ -131,7 +131,7 @@
                     zoomControl.minZoom,
                     zoomControl.maxZoom
                 )
-                cameraStateAdapter.setZoomState(zoomValue)
+                cameraControlStateAdapter.setZoomState(zoomValue)
             }
         }.asListenableFuture()
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlStateAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlStateAdapter.kt
new file mode 100644
index 0000000..6e3e98c
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlStateAdapter.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.camera.camera2.pipe.integration.adapter
+
+import android.annotation.SuppressLint
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.camera2.pipe.integration.impl.EvCompControl
+import androidx.camera.camera2.pipe.integration.impl.TorchControl
+import androidx.camera.camera2.pipe.integration.impl.ZoomControl
+import androidx.camera.core.ExposureState
+import androidx.camera.core.ZoomState
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import javax.inject.Inject
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+/**
+ * [CameraControlStateAdapter] caches and updates based on callbacks from the active CameraGraph.
+ */
+@SuppressLint("UnsafeOptInUsageError")
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@CameraScope
+class CameraControlStateAdapter @Inject constructor(
+    private val zoomControl: ZoomControl,
+    private val evCompControl: EvCompControl,
+    private val torchControl: TorchControl,
+) {
+    val torchStateLiveData: LiveData<Int>
+        get() = torchControl.torchStateLiveData
+
+    private val _zoomState by lazy {
+        MutableLiveData<ZoomState>(
+            ZoomValue(
+                zoomControl.zoomRatio,
+                zoomControl.minZoom,
+                zoomControl.maxZoom
+            )
+        )
+    }
+    val zoomStateLiveData: LiveData<ZoomState>
+        get() = _zoomState
+
+    suspend fun setZoomState(value: ZoomState) {
+        withContext(Dispatchers.Main) {
+            _zoomState.value = value
+        }
+    }
+
+    val exposureState: ExposureState
+        get() = evCompControl.exposureState
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
index 3f8b947..2f49b0ac 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
@@ -43,7 +43,6 @@
 import androidx.camera.core.impl.Timebase
 import androidx.camera.core.impl.utils.CameraOrientationUtil
 import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -59,7 +58,8 @@
 class CameraInfoAdapter @Inject constructor(
     private val cameraProperties: CameraProperties,
     private val cameraConfig: CameraConfig,
-    private val cameraState: CameraStateAdapter,
+    private val cameraStateAdapter: CameraStateAdapter,
+    private val cameraControlStateAdapter: CameraControlStateAdapter,
     private val cameraCallbackMap: CameraCallbackMap,
 ) : CameraInfoInternal {
     private lateinit var camcorderProfileProviderAdapter: CamcorderProfileProviderAdapter
@@ -94,16 +94,13 @@
         )
     }
 
-    override fun getZoomState(): LiveData<ZoomState> = cameraState.zoomStateLiveData
-    override fun getTorchState(): LiveData<Int> = cameraState.torchStateLiveData
+    override fun getZoomState(): LiveData<ZoomState> = cameraControlStateAdapter.zoomStateLiveData
+    override fun getTorchState(): LiveData<Int> = cameraControlStateAdapter.torchStateLiveData
 
     @SuppressLint("UnsafeOptInUsageError")
-    override fun getExposureState(): ExposureState = cameraState.exposureState
+    override fun getExposureState(): ExposureState = cameraControlStateAdapter.exposureState
 
-    override fun getCameraState(): LiveData<CameraState> {
-        Log.warn { "TODO: CameraState is not yet supported." }
-        return MutableLiveData(CameraState.create(CameraState.Type.CLOSED))
-    }
+    override fun getCameraState(): LiveData<CameraState> = cameraStateAdapter.cameraState
 
     override fun addSessionCaptureCallback(executor: Executor, callback: CameraCaptureCallback) =
         cameraCallbackMap.addCaptureCallback(callback, executor)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
index b4bcd62..c929ca0 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
@@ -30,7 +30,6 @@
 import androidx.camera.core.impl.CameraControlInternal
 import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.CameraInternal
-import androidx.camera.core.impl.LiveDataObservable
 import androidx.camera.core.impl.Observable
 import com.google.common.util.concurrent.ListenableFuture
 import javax.inject.Inject
@@ -49,16 +48,14 @@
     private val cameraInfo: CameraInfoInternal,
     private val cameraController: CameraControlInternal,
     private val threads: UseCaseThreads,
+    private val cameraStateAdapter: CameraStateAdapter
 ) : CameraInternal {
     private val cameraId = config.cameraId
     private var coreCameraConfig: androidx.camera.core.impl.CameraConfig =
         CameraConfigs.emptyConfig()
     private val debugId = cameraAdapterIds.incrementAndGet()
-    private val cameraState = LiveDataObservable<CameraInternal.State>()
 
     init {
-        cameraState.postValue(CameraInternal.State.CLOSED)
-
         debug { "Created $this for $cameraId" }
         // TODO: Consider preloading the list of camera ids and metadata.
     }
@@ -78,7 +75,9 @@
     }
 
     override fun getCameraInfoInternal(): CameraInfoInternal = cameraInfo
-    override fun getCameraState(): Observable<CameraInternal.State> = cameraState
+    override fun getCameraState(): Observable<CameraInternal.State> =
+        cameraStateAdapter.cameraInternalState
+
     override fun getCameraControlInternal(): CameraControlInternal = cameraController
 
     // UseCase attach / detach behaviors.
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapter.kt
index e4cc398..28de2a6 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapter.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,51 +16,128 @@
 
 package androidx.camera.camera2.pipe.integration.adapter
 
-import android.annotation.SuppressLint
+import android.os.Looper
+import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.GraphState
+import androidx.camera.camera2.pipe.GraphState.GraphStateStarted
+import androidx.camera.camera2.pipe.GraphState.GraphStateStarting
+import androidx.camera.camera2.pipe.GraphState.GraphStateStopped
+import androidx.camera.camera2.pipe.GraphState.GraphStateStopping
+import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.config.CameraScope
-import androidx.camera.camera2.pipe.integration.impl.EvCompControl
-import androidx.camera.camera2.pipe.integration.impl.TorchControl
-import androidx.camera.camera2.pipe.integration.impl.ZoomControl
-import androidx.camera.core.ExposureState
-import androidx.camera.core.ZoomState
-import androidx.lifecycle.LiveData
+import androidx.camera.core.CameraState
+import androidx.camera.core.impl.CameraInternal
+import androidx.camera.core.impl.LiveDataObservable
 import androidx.lifecycle.MutableLiveData
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
 import javax.inject.Inject
 
-/**
- * [CameraStateAdapter] caches and updates based on callbacks from the active CameraGraph.
- */
-@SuppressLint("UnsafeOptInUsageError")
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 @CameraScope
-class CameraStateAdapter @Inject constructor(
-    private val zoomControl: ZoomControl,
-    private val evCompControl: EvCompControl,
-    private val torchControl: TorchControl,
-) {
-    val torchStateLiveData: LiveData<Int>
-        get() = torchControl.torchStateLiveData
+@RequiresApi(21)
+class CameraStateAdapter @Inject constructor() {
+    private val lock = Any()
 
-    private val _zoomState by lazy {
-        MutableLiveData<ZoomState>(
-            ZoomValue(
-                zoomControl.zoomRatio,
-                zoomControl.minZoom,
-                zoomControl.maxZoom
-            )
-        )
+    internal val cameraInternalState = LiveDataObservable<CameraInternal.State>()
+    internal val cameraState = MutableLiveData<CameraState>()
+
+    @GuardedBy("lock")
+    private var currentGraph: CameraGraph? = null
+
+    @GuardedBy("lock")
+    private var currentGraphState: GraphState = GraphStateStopped
+
+    init {
+        postCameraState(CameraInternal.State.CLOSED)
     }
-    val zoomStateLiveData: LiveData<ZoomState>
-        get() = _zoomState
-    suspend fun setZoomState(value: ZoomState) {
-        withContext(Dispatchers.Main) {
-            _zoomState.value = value
+
+    public fun onGraphUpdated(cameraGraph: CameraGraph) = synchronized(lock) {
+        Log.debug { "Camera graph updated from $currentGraph to $cameraGraph" }
+        if (currentGraphState != GraphStateStopped) {
+            postCameraState(CameraInternal.State.CLOSING)
+            postCameraState(CameraInternal.State.CLOSED)
+        }
+        currentGraph = cameraGraph
+        currentGraphState = GraphStateStopped
+    }
+
+    public fun onGraphStateUpdated(cameraGraph: CameraGraph, graphState: GraphState) =
+        synchronized(lock) {
+            Log.debug { "$cameraGraph state updated to $graphState" }
+            handleStateTransition(cameraGraph, graphState)
+        }
+
+    @GuardedBy("lock")
+    private fun handleStateTransition(cameraGraph: CameraGraph, graphState: GraphState) {
+        // If the transition came from a different camera graph, consider it stale and ignore it.
+        if (cameraGraph != currentGraph) {
+            Log.debug { "Ignored stale transition $graphState for $cameraGraph" }
+            return
+        }
+
+        if (!isTransitionPermissible(currentGraphState, graphState)) {
+            Log.warn { "Impermissible state transition from $currentGraphState to $graphState" }
+            return
+        }
+        currentGraphState = graphState
+
+        // Now that the current graph state is updated, post the latest states.
+        Log.debug { "Updated current graph state to $currentGraphState" }
+        postCameraState(currentGraphState.toCameraInternalState())
+    }
+
+    private fun postCameraState(internalState: CameraInternal.State) {
+        cameraInternalState.postValue(internalState)
+        cameraState.setOrPostValue(CameraState.create(internalState.toCameraState()))
+    }
+
+    private fun isTransitionPermissible(oldState: GraphState, newState: GraphState): Boolean {
+        return when (oldState) {
+            GraphStateStarting ->
+                newState == GraphStateStarted ||
+                    newState == GraphStateStopping ||
+                    newState == GraphStateStopped
+
+            GraphStateStarted ->
+                newState == GraphStateStopping ||
+                    newState == GraphStateStopped
+
+            GraphStateStopping ->
+                newState == GraphStateStopped ||
+                    newState == GraphStateStarting
+
+            GraphStateStopped ->
+                newState == GraphStateStarting ||
+                    newState == GraphStateStarted
+
+            else -> false
         }
     }
+}
 
-    val exposureState: ExposureState
-        get() = evCompControl.exposureState
+@RequiresApi(21)
+internal fun GraphState.toCameraInternalState(): CameraInternal.State = when (this) {
+    GraphStateStarting -> CameraInternal.State.OPENING
+    GraphStateStarted -> CameraInternal.State.OPEN
+    GraphStateStopping -> CameraInternal.State.CLOSING
+    GraphStateStopped -> CameraInternal.State.CLOSED
+    else -> throw IllegalArgumentException("Unexpected graph state: $this")
+}
+
+@RequiresApi(21)
+internal fun CameraInternal.State.toCameraState(): CameraState.Type = when (this) {
+    CameraInternal.State.CLOSED -> CameraState.Type.CLOSED
+    CameraInternal.State.OPENING -> CameraState.Type.OPENING
+    CameraInternal.State.OPEN -> CameraState.Type.OPEN
+    CameraInternal.State.CLOSING -> CameraState.Type.CLOSING
+    CameraInternal.State.PENDING_OPEN -> CameraState.Type.PENDING_OPEN
+    else -> throw IllegalArgumentException("Unexpected CameraInternal state: $this")
+}
+
+internal fun MutableLiveData<CameraState>.setOrPostValue(cameraState: CameraState) {
+    if (Looper.myLooper() == Looper.getMainLooper()) {
+        this.value = cameraState
+    } else {
+        this.postValue(cameraState)
+    }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
index bfef920..f529114 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
@@ -26,6 +26,7 @@
 import androidx.camera.camera2.pipe.StreamFormat
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter.Companion.toCamera2ImplConfig
 import androidx.camera.camera2.pipe.integration.impl.CameraCallbackMap
@@ -46,11 +47,13 @@
 annotation class UseCaseCameraScope
 
 /** Dependency bindings for building a [UseCaseCamera] */
-@Module(includes = [
-    CapturePipelineImpl.Bindings::class,
-    UseCaseCameraImpl.Bindings::class,
-    UseCaseCameraRequestControlImpl.Bindings::class,
-])
+@Module(
+    includes = [
+        CapturePipelineImpl.Bindings::class,
+        UseCaseCameraImpl.Bindings::class,
+        UseCaseCameraRequestControlImpl.Bindings::class,
+    ]
+)
 abstract class UseCaseCameraModule {
     // Used for dagger provider methods that are static.
     companion object
@@ -59,7 +62,8 @@
 /** Dagger module for binding the [UseCase]'s to the [UseCaseCamera]. */
 @Module
 class UseCaseCameraConfig(
-    private val useCases: List<UseCase>
+    private val useCases: List<UseCase>,
+    private val cameraStateAdapter: CameraStateAdapter,
 ) {
     @UseCaseCameraScope
     @Provides
@@ -105,11 +109,13 @@
         }
 
         // Build up a config (using TEMPLATE_PREVIEW by default)
-        val graph = cameraPipe.create(CameraGraph.Config(
-            camera = cameraConfig.cameraId,
-            streams = streamConfigMap.keys.toList(),
-            defaultListeners = listOf(callbackMap, requestListener),
-        ))
+        val graph = cameraPipe.create(
+            CameraGraph.Config(
+                camera = cameraConfig.cameraId,
+                streams = streamConfigMap.keys.toList(),
+                defaultListeners = listOf(callbackMap, requestListener),
+            )
+        )
 
         val surfaceToStreamMap = mutableMapOf<DeferrableSurface, StreamId>()
         streamConfigMap.forEach { (streamConfig, deferrableSurface) ->
@@ -138,6 +144,7 @@
         return UseCaseGraphConfig(
             graph = graph,
             surfaceToStreamMap = surfaceToStreamMap,
+            cameraStateAdapter = cameraStateAdapter,
         )
     }
 }
@@ -145,6 +152,7 @@
 data class UseCaseGraphConfig(
     val graph: CameraGraph,
     val surfaceToStreamMap: Map<DeferrableSurface, StreamId>,
+    val cameraStateAdapter: CameraStateAdapter,
 )
 
 /** Dagger subcomponent for a single [UseCaseCamera] instance. */
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt
index af35e8f..5bcc860 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt
@@ -16,12 +16,24 @@
 
 package androidx.camera.camera2.pipe.integration.impl
 
+import android.hardware.camera2.CameraCaptureSession
 import android.hardware.camera2.CaptureFailure
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.TotalCaptureResult
+import android.os.Build
+import android.view.Surface
+import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraTimestamp
 import androidx.camera.camera2.pipe.FrameInfo
+import androidx.camera.camera2.pipe.FrameMetadata
 import androidx.camera.camera2.pipe.FrameNumber
 import androidx.camera.camera2.pipe.Request
 import androidx.camera.camera2.pipe.RequestMetadata
+import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.adapter.CameraUseCaseAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CaptureResultAdapter
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.core.impl.CameraCaptureCallback
@@ -36,6 +48,7 @@
 @CameraScope
 class CameraCallbackMap @Inject constructor() : Request.Listener {
     private val callbackMap = mutableMapOf<CameraCaptureCallback, Executor>()
+
     @Volatile
     private var callbacks: Map<CameraCaptureCallback, Executor> = mapOf()
 
@@ -55,14 +68,56 @@
         }
     }
 
+    override fun onBufferLost(
+        requestMetadata: RequestMetadata,
+        frameNumber: FrameNumber,
+        stream: StreamId
+    ) {
+        for ((callback, executor) in callbacks) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
+                callback is CameraUseCaseAdapter.CaptureCallbackContainer
+            ) {
+                val session: CameraCaptureSession? =
+                    requestMetadata.unwrapAs(CameraCaptureSession::class)
+                val request: CaptureRequest? = requestMetadata.unwrapAs(CaptureRequest::class)
+                val surface: Surface? = requestMetadata.streams[stream]
+                if (session != null && request != null && surface != null) {
+                    executor.execute {
+                        Api24CompatImpl.onCaptureBufferLost(
+                            callback.captureCallback, session, request, surface, frameNumber.value
+                        )
+                    }
+                }
+            } else {
+                Log.error { "Unhandled callback for onBufferLost()" }
+            }
+        }
+    }
+
     override fun onComplete(
         requestMetadata: RequestMetadata,
         frameNumber: FrameNumber,
         result: FrameInfo
     ) {
-        val captureResult = CaptureResultAdapter(requestMetadata, frameNumber, result)
         for ((callback, executor) in callbacks) {
-            executor.execute { callback.onCaptureCompleted(captureResult) }
+            if (callback is CameraUseCaseAdapter.CaptureCallbackContainer) {
+                val session: CameraCaptureSession? =
+                    requestMetadata.unwrapAs(CameraCaptureSession::class)
+                val request: CaptureRequest? = requestMetadata.unwrapAs(CaptureRequest::class)
+                val totalCaptureResult: TotalCaptureResult? =
+                    result.unwrapAs(TotalCaptureResult::class)
+                if (session != null && request != null && totalCaptureResult != null) {
+                    executor.execute {
+                        callback.captureCallback.onCaptureCompleted(
+                            session, request,
+                            totalCaptureResult
+                        )
+                    }
+                }
+            } else {
+                val captureResult = CaptureResultAdapter(requestMetadata, frameNumber, result)
+                executor.execute { callback.onCaptureCompleted(captureResult) }
+            }
         }
     }
 
@@ -71,9 +126,23 @@
         frameNumber: FrameNumber,
         captureFailure: CaptureFailure
     ) {
-        val failure = CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR)
         for ((callback, executor) in callbacks) {
-            executor.execute { callback.onCaptureFailed(failure) }
+            if (callback is CameraUseCaseAdapter.CaptureCallbackContainer) {
+                val session: CameraCaptureSession? =
+                    requestMetadata.unwrapAs(CameraCaptureSession::class)
+                val request: CaptureRequest? = requestMetadata.unwrapAs(CaptureRequest::class)
+                if (session != null && request != null) {
+                    executor.execute {
+                        callback.captureCallback.onCaptureFailed(
+                            session, request,
+                            captureFailure
+                        )
+                    }
+                }
+            } else {
+                val failure = CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR)
+                executor.execute { callback.onCaptureFailed(failure) }
+            }
         }
     }
 
@@ -82,4 +151,107 @@
             executor.execute { callback.onCaptureCancelled() }
         }
     }
+
+    override fun onPartialCaptureResult(
+        requestMetadata: RequestMetadata,
+        frameNumber: FrameNumber,
+        captureResult: FrameMetadata
+    ) {
+        for ((callback, executor) in callbacks) {
+            if (callback is CameraUseCaseAdapter.CaptureCallbackContainer) {
+                val session: CameraCaptureSession? =
+                    requestMetadata.unwrapAs(CameraCaptureSession::class)
+                val request: CaptureRequest? = requestMetadata.unwrapAs(CaptureRequest::class)
+                val partialResult: CaptureResult? = captureResult.unwrapAs(CaptureResult::class)
+                if (session != null && request != null && partialResult != null) {
+                    executor.execute {
+                        callback.captureCallback.onCaptureProgressed(
+                            session, request, partialResult
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    override fun onRequestSequenceAborted(requestMetadata: RequestMetadata) {
+        for ((callback, executor) in callbacks) {
+            if (callback is CameraUseCaseAdapter.CaptureCallbackContainer) {
+                val session: CameraCaptureSession? =
+                    requestMetadata.unwrapAs(CameraCaptureSession::class)
+                val request: CaptureRequest? = requestMetadata.unwrapAs(CaptureRequest::class)
+                if (session != null && request != null) {
+                    executor.execute {
+                        callback.captureCallback.onCaptureSequenceAborted(
+                            session, -1 /*sequenceId*/
+                        )
+                    }
+                }
+            } else {
+                executor.execute { callback.onCaptureCancelled() }
+            }
+        }
+    }
+
+    override fun onRequestSequenceCompleted(
+        requestMetadata: RequestMetadata,
+        frameNumber: FrameNumber
+    ) {
+        for ((callback, executor) in callbacks) {
+            if (callback is CameraUseCaseAdapter.CaptureCallbackContainer) {
+                val session: CameraCaptureSession? =
+                    requestMetadata.unwrapAs(CameraCaptureSession::class)
+                val request: CaptureRequest? = requestMetadata.unwrapAs(CaptureRequest::class)
+                if (session != null && request != null) {
+                    executor.execute {
+                        callback.captureCallback.onCaptureSequenceCompleted(
+                            session, -1 /*sequenceId*/, frameNumber.value
+                        )
+                    }
+                }
+            } else {
+                Log.error { "Unhandled callback for onRequestSequenceCompleted()" }
+            }
+        }
+    }
+
+    override fun onStarted(
+        requestMetadata: RequestMetadata,
+        frameNumber: FrameNumber,
+        timestamp: CameraTimestamp
+    ) {
+        for ((callback, executor) in callbacks) {
+            if (callback is CameraUseCaseAdapter.CaptureCallbackContainer) {
+                val session: CameraCaptureSession? =
+                    requestMetadata.unwrapAs(CameraCaptureSession::class)
+                val request: CaptureRequest? = requestMetadata.unwrapAs(CaptureRequest::class)
+                if (session != null && request != null) {
+                    executor.execute {
+                        callback.captureCallback.onCaptureStarted(
+                            session, request, timestamp.value, frameNumber.value
+                        )
+                    }
+                }
+            } else {
+                Log.error { "Unhandled callback for onStarted()" }
+            }
+        }
+    }
+
+    @RequiresApi(24)
+    private object Api24CompatImpl {
+        @DoNotInline
+        @JvmStatic
+        fun onCaptureBufferLost(
+            callback: CameraCaptureSession.CaptureCallback,
+            session: CameraCaptureSession,
+            request: CaptureRequest,
+            surface: Surface,
+            frameNumber: Long
+        ) {
+            callback.onCaptureBufferLost(
+                session, request, surface, frameNumber
+            )
+        }
+    }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
index 561805c..9ab3c3c 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
@@ -85,6 +85,7 @@
     override val requestControl: UseCaseCameraRequestControl,
 ) : UseCaseCamera {
     private val debugId = useCaseCameraIds.incrementAndGet()
+    private val graphStateJob: Job
 
     override var runningUseCases = setOf<UseCase>()
         set(value) {
@@ -105,9 +106,20 @@
 
     init {
         debug { "Configured $this for $useCases" }
+        useCaseGraphConfig.apply {
+            cameraStateAdapter.onGraphUpdated(graph)
+        }
+        graphStateJob = threads.scope.launch {
+            useCaseGraphConfig.apply {
+                graph.graphState.collect {
+                    cameraStateAdapter.onGraphStateUpdated(graph, it)
+                }
+            }
+        }
     }
 
     override fun close(): Job {
+        graphStateJob.cancel()
         return threads.scope.launch {
             debug { "Closing $this" }
             useCaseGraphConfig.graph.close()
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index 537adf9..7021369 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraComponent
@@ -66,8 +67,9 @@
     @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") // Java version required for Dagger
     private val controls: java.util.Set<UseCaseCameraControl>,
     private val camera2CameraControl: Camera2CameraControl,
+    private val cameraStateAdapter: CameraStateAdapter,
     cameraProperties: CameraProperties,
-    displayInfoManager: DisplayInfoManager
+    displayInfoManager: DisplayInfoManager,
 ) {
     private val lock = Any()
 
@@ -242,7 +244,7 @@
         }
 
         // Create and configure the new camera component.
-        _activeComponent = builder.config(UseCaseCameraConfig(useCases)).build()
+        _activeComponent = builder.config(UseCaseCameraConfig(useCases, cameraStateAdapter)).build()
         for (control in allControls) {
             control.useCaseCamera = camera
         }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapterTest.kt
new file mode 100644
index 0000000..7056538
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapterTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.adapter
+
+import android.os.Build
+import androidx.camera.camera2.pipe.GraphState.GraphStateStarted
+import androidx.camera.camera2.pipe.GraphState.GraphStateStarting
+import androidx.camera.camera2.pipe.GraphState.GraphStateStopped
+import androidx.camera.camera2.pipe.GraphState.GraphStateStopping
+import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
+import androidx.camera.core.CameraState
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+internal class CameraStateAdapterTest {
+    private val cameraStateAdapter = CameraStateAdapter()
+    private val cameraGraph1 = FakeCameraGraph()
+    private val cameraGraph2 = FakeCameraGraph()
+
+    @Test
+    fun testNormalStateTransitions() {
+        cameraStateAdapter.onGraphUpdated(cameraGraph1)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarting)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPENING)
+
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarted)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPEN)
+
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStopped)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+    }
+
+    @Test
+    fun testStaleStateTransitions() {
+        cameraStateAdapter.onGraphUpdated(cameraGraph1)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarting)
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarted)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPEN)
+
+        // Simulate that a new camera graph is created.
+        cameraStateAdapter.onGraphUpdated(cameraGraph2)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph2, GraphStateStarting)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPENING)
+
+        // This came from cameraGraph1 and thereby making the transition stale.
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStopped)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPENING)
+
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph2, GraphStateStarted)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPEN)
+    }
+
+    @Test
+    fun testImpermissibleStateTransitions() {
+        cameraStateAdapter.onGraphUpdated(cameraGraph1)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+
+        // Impermissible state transition from GraphStateStopped to GraphStateStopping
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStopping)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarting)
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarted)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPEN)
+
+        // Impermissible state transition from GraphStateStarted to GraphStateStarting
+        cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarting)
+        assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPEN)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt
index 252f425..34c2fa3 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt
@@ -33,6 +33,7 @@
 import androidx.camera.core.impl.TagBundle
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executors
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
@@ -43,7 +44,6 @@
 import org.junit.runner.RunWith
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
-import java.util.concurrent.Executors
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
@@ -66,6 +66,7 @@
         useCaseGraphConfig = UseCaseGraphConfig(
             graph = FakeCameraGraph(),
             surfaceToStreamMap = mapOf(surface to StreamId(0)),
+            cameraStateAdapter = CameraStateAdapter(),
         ),
         cameraProperties = fakeCameraProperties,
         threads = fakeUseCaseThreads,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
index 2b64455..f21cfe8 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
@@ -31,6 +31,7 @@
 import androidx.camera.camera2.pipe.Request
 import androidx.camera.camera2.pipe.RequestTemplate
 import androidx.camera.camera2.pipe.Result3A
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.integration.adapter.asListenableFuture
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
@@ -47,6 +48,11 @@
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.impl.utils.futures.Futures
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
@@ -63,11 +69,6 @@
 import org.mockito.Mockito.mock
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
-import java.util.concurrent.ExecutionException
-import java.util.concurrent.Executors
-import java.util.concurrent.ScheduledFuture
-import java.util.concurrent.Semaphore
-import java.util.concurrent.TimeUnit
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @DoNotInstrument
@@ -199,6 +200,7 @@
             useCaseGraphConfig = UseCaseGraphConfig(
                 graph = FakeCameraGraph(fakeCameraGraphSession = fakeCameraGraphSession),
                 surfaceToStreamMap = emptyMap(),
+                cameraStateAdapter = CameraStateAdapter(),
             ),
         )
     }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
index f8902eb..0d3dc40 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
@@ -24,6 +24,7 @@
 import androidx.camera.camera2.pipe.Request
 import androidx.camera.camera2.pipe.RequestMetadata
 import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
@@ -39,6 +40,8 @@
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.TagBundle
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
@@ -48,8 +51,6 @@
 import org.junit.runner.RunWith
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
@@ -72,6 +73,7 @@
     private val fakeUseCaseGraphConfig = UseCaseGraphConfig(
         graph = fakeCameraGraph,
         surfaceToStreamMap = surfaceToStreamMap,
+        cameraStateAdapter = CameraStateAdapter(),
     )
     private val fakeConfigAdapter = CaptureConfigAdapter(
         useCaseGraphConfig = fakeUseCaseGraphConfig,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
index 6126329..00097a2 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
@@ -20,6 +20,7 @@
 import android.os.Build
 import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
@@ -68,6 +69,7 @@
     private val fakeUseCaseGraphConfig = UseCaseGraphConfig(
         graph = fakeCameraGraph,
         surfaceToStreamMap = surfaceToStreamMap,
+        cameraStateAdapter = CameraStateAdapter(),
     )
     private val fakeConfigAdapter = CaptureConfigAdapter(
         useCaseGraphConfig = fakeUseCaseGraphConfig,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
index 13ef7aa..a98af2c 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
@@ -185,6 +186,7 @@
             useCaseThreads,
             ComboRequestListener()
         ),
-        displayInfoManager = DisplayInfoManager(ApplicationProvider.getApplicationContext())
+        cameraStateAdapter = CameraStateAdapter(),
+        displayInfoManager = DisplayInfoManager(ApplicationProvider.getApplicationContext()),
     )
 }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
index b99fa69..0aafb22 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
@@ -19,6 +19,7 @@
 import android.hardware.camera2.CameraCharacteristics
 import android.os.Build
 import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.integration.adapter.CameraControlStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CameraInfoAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
@@ -198,7 +199,8 @@
         return CameraInfoAdapter(
             cameraProperties,
             CameraConfig(cameraId),
-            CameraStateAdapter(
+            CameraStateAdapter(),
+            CameraControlStateAdapter(
                 ZoomControl(FakeZoomCompat()),
                 EvCompControl(FakeEvCompCompat()),
                 TorchControl(cameraProperties, useCaseThreads),
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
index c4c65e5..3b3ad4c 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
@@ -22,6 +22,7 @@
 import androidx.camera.camera2.pipe.RequestTemplate
 import androidx.camera.camera2.pipe.Result3A
 import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraComponent
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
 import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
@@ -35,7 +36,7 @@
 import kotlinx.coroutines.Job
 
 class FakeUseCaseCameraComponentBuilder : UseCaseCameraComponent.Builder {
-    private var config: UseCaseCameraConfig = UseCaseCameraConfig(emptyList())
+    private var config: UseCaseCameraConfig = UseCaseCameraConfig(emptyList(), CameraStateAdapter())
 
     override fun config(config: UseCaseCameraConfig): UseCaseCameraComponent.Builder {
         this.config = config
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
index 267b739..b658684 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
@@ -196,6 +196,7 @@
 
             @Suppress("SyntheticAccessor")
             val metadata = Camera2RequestMetadata(
+                session,
                 captureRequest,
                 defaultParameters,
                 requiredParameters,
@@ -280,6 +281,7 @@
 @RequiresApi(21)
 @Suppress("SyntheticAccessor") // Using an inline class generates a synthetic constructor
 internal class Camera2RequestMetadata(
+    private val cameraCaptureSessionWrapper: CameraCaptureSessionWrapper,
     private val captureRequest: CaptureRequest,
     private val defaultParameters: Map<*, Any?>,
     private val requiredParameters: Map<*, Any?>,
@@ -298,9 +300,11 @@
         requiredParameters.containsKey(key) -> {
             requiredParameters[key] as T?
         }
+
         request.extras.containsKey(key) -> {
             request.extras[key] as T?
         }
+
         else -> {
             defaultParameters[key] as T?
         }
@@ -311,6 +315,9 @@
     @Suppress("UNCHECKED_CAST")
     override fun <T : Any> unwrapAs(type: KClass<T>): T? = when (type) {
         CaptureRequest::class -> captureRequest as T
+        CameraCaptureSession::class ->
+            cameraCaptureSessionWrapper.unwrapAs(CameraCaptureSession::class) as? T
+
         else -> null
     }
 }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessorTest.kt
index 1bf84cc..1cb7e71 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessorTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessorTest.kt
@@ -17,6 +17,7 @@
 package androidx.camera.camera2.pipe.compat
 
 import android.graphics.SurfaceTexture
+import android.hardware.camera2.CameraCaptureSession
 import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
 import android.hardware.camera2.CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
 import android.hardware.camera2.CaptureRequest
@@ -98,7 +99,8 @@
     private val stream2 = streamGraph[stream2Config]!!
 
     private val fakeCameraDevice = FakeCameraDeviceWrapper(testCamera)
-    private val fakeCaptureSession = fakeCameraDevice.createFakeCaptureSession()
+    private val fakeCaptureSessionWrapper =
+        fakeCameraDevice.createFakeCaptureSession(null)
 
     @After
     fun teardown() {
@@ -137,7 +139,7 @@
     @Test
     fun requestIsCreatedAndSubmitted() = runTest {
         val captureSequenceProcessor = Camera2CaptureSequenceProcessor(
-            fakeCaptureSession,
+            fakeCaptureSessionWrapper,
             FakeThreads.fromTestScope(this),
             RequestTemplate(1),
             mapOf(
@@ -163,8 +165,8 @@
         val result = captureSequenceProcessor.submit(sequence!!)
 
         assertThat(result).isGreaterThan(0)
-        assertThat(fakeCaptureSession.lastCapture).hasSize(1)
-        assertThat(fakeCaptureSession.lastRepeating).isNull()
+        assertThat(fakeCaptureSessionWrapper.lastCapture).hasSize(1)
+        assertThat(fakeCaptureSessionWrapper.lastRepeating).isNull()
 
         // TODO: Add support for checking parameters when robolectric supports it.
     }
@@ -172,7 +174,7 @@
     @Test
     fun requestIsSubmittedWithPartialSurfaces() = runTest {
         val captureSequenceProcessor = Camera2CaptureSequenceProcessor(
-            fakeCaptureSession,
+            fakeCaptureSessionWrapper,
             FakeThreads.fromTestScope(this),
             RequestTemplate(1),
             mapOf(
@@ -196,7 +198,7 @@
     @Test
     fun requestIsNotSubmittedWithEmptySurfaceList() = runTest {
         val captureSequenceProcessor = Camera2CaptureSequenceProcessor(
-            fakeCaptureSession,
+            fakeCaptureSessionWrapper,
             FakeThreads.fromTestScope(this),
             RequestTemplate(1),
             mapOf(
@@ -216,4 +218,32 @@
 
         assertThat(captureSequence).isNull()
     }
+
+    @Test
+    fun requestMetaDataUnwrapsAsCameraCaptureSession() = runTest {
+        val captureSequenceProcessor = Camera2CaptureSequenceProcessor(
+            fakeCaptureSessionWrapper,
+            FakeThreads.fromTestScope(this),
+            RequestTemplate(1),
+            mapOf(
+                stream1.id to surface1
+            )
+        )
+        val captureSequence = captureSequenceProcessor.build(
+            isRepeating = false,
+            requests = listOf(Request(listOf(stream1.id, stream2.id))),
+            defaultParameters = mapOf<Any, Any?>(),
+            requiredParameters = mapOf<Any, Any?>(),
+            listeners = emptyList(),
+            sequenceListener = FakeCaptureSequenceListener()
+        )
+
+        assertThat(captureSequence).isNotNull()
+        assertThat(captureSequence!!.captureMetadataList).isNotEmpty()
+        captureSequence.captureMetadataList[0].unwrapAs(CameraCaptureSession::class)
+
+        assertThat(fakeCaptureSessionWrapper.unwrappedClasses.size).isEqualTo(1)
+        assertThat(fakeCaptureSessionWrapper.unwrappedClasses[0])
+            .isEqualTo(CameraCaptureSession::class)
+    }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCaptureSessionWrapper.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCaptureSessionWrapper.kt
index 63ba737..60b41dd 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCaptureSessionWrapper.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCaptureSessionWrapper.kt
@@ -28,7 +28,7 @@
 internal class FakeCaptureSessionWrapper(
     override val device: CameraDeviceWrapper,
     override val isReprocessable: Boolean = false,
-    override val inputSurface: Surface? = null
+    override val inputSurface: Surface? = null,
 ) : CameraCaptureSessionWrapper {
     var closed = false
     var lastSequenceNumber = 0
@@ -41,6 +41,8 @@
     var stopRepeatingInvoked = false
     var abortCapturesInvoked = false
 
+    val unwrappedClasses = arrayListOf<Any>()
+
     override fun abortCaptures() {
         abortCapturesInvoked = true
     }
@@ -103,7 +105,11 @@
         )
     }
 
-    override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
+    override fun <T : Any> unwrapAs(type: KClass<T>): T? {
+        unwrappedClasses.add(type)
+        return null
+    }
+
     override fun close() {
         closed = true
     }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedOutputSizesCollector.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedOutputSizesCollector.java
index 02e0614..589530a 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedOutputSizesCollector.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedOutputSizesCollector.java
@@ -173,13 +173,7 @@
         // result.
         Arrays.sort(outputSizes, new CompareSizesByArea(true));
 
-        // Removes the duplicate items
-        List<Size> resultList = new ArrayList<>();
-        for (Size size: outputSizes) {
-            if (!resultList.contains(size)) {
-                resultList.add(size);
-            }
-        }
+        List<Size> resultList = Arrays.asList(outputSizes);
 
         if (resultList.isEmpty()) {
             throw new IllegalArgumentException(
@@ -700,6 +694,12 @@
 
                 indexBigEnough = i;
             } else {
+                // If duplicated miniBoundingSize items exist in the list, the size will be added
+                // into the removeSizes list. Removes it from the removeSizes list to keep the
+                // miniBoundingSize items in the final result list.
+                if (indexBigEnough >= 0) {
+                    removeSizes.remove(supportedSizesList.get(indexBigEnough));
+                }
                 break;
             }
         }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 8d9b4ed..df23742 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -1659,8 +1659,10 @@
         // RejectedExecutionException if a ProcessingImageReader is used to processing the
         // captured images.
         ExecutorService executorService = mExecutor;
-        imageReaderCloseFuture.addListener(executorService::shutdown,
-                CameraXExecutors.directExecutor());
+        if (executorService != null) {
+            imageReaderCloseFuture.addListener(executorService::shutdown,
+                    CameraXExecutors.directExecutor());
+        }
     }
 
     /**
@@ -1851,7 +1853,9 @@
      *  {@link ImagePipeline}/{@link TakePictureManager}.
      */
 
+    @Nullable
     private ImagePipeline mImagePipeline;
+    @Nullable
     private TakePictureManager mTakePictureManager;
 
     /**
@@ -2055,9 +2059,11 @@
     private void clearPipelineWithNode(boolean keepTakePictureManager) {
         Log.d(TAG, "clearPipelineWithNode");
         checkMainThread();
-        mImagePipeline.close();
-        mImagePipeline = null;
-        if (!keepTakePictureManager) {
+        if (mImagePipeline != null) {
+            mImagePipeline.close();
+            mImagePipeline = null;
+        }
+        if (!keepTakePictureManager && mTakePictureManager != null) {
             mTakePictureManager.abortRequests();
             mTakePictureManager = null;
         }
@@ -2104,7 +2110,7 @@
     @VisibleForTesting
     @NonNull
     TakePictureManager getTakePictureManager() {
-        return mTakePictureManager;
+        return requireNonNull(mTakePictureManager);
     }
 
     // ===== New architecture end =====
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index f266b2d..585948a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -323,7 +323,7 @@
                 /*hasEmbeddedTransform=*/true,
                 requireNonNull(getCropRect(resolution)),
                 getRelativeRotation(camera),
-                /*mirroring=*/true,
+                /*mirroring=*/isFrontCamera(camera),
                 this::notifyReset);
         SurfaceEdge inputEdge = SurfaceEdge.create(singletonList(cameraSurface));
         SurfaceEdge outputEdge = mNode.transform(inputEdge);
@@ -343,6 +343,11 @@
         return sessionConfigBuilder;
     }
 
+    private static boolean isFrontCamera(@NonNull CameraInternal camera) {
+        Integer lensFacing = camera.getCameraInfoInternal().getLensFacing();
+        return lensFacing != null && lensFacing == CameraSelector.LENS_FACING_FRONT;
+    }
+
     /**
      * Sets a {@link SurfaceProcessorInternal}.
      *
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
index 3848aa5..5008b5c 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
@@ -16,8 +16,13 @@
 
 package androidx.camera.core;
 
+import static androidx.camera.core.CameraEffect.IMAGE_CAPTURE;
+import static androidx.camera.core.CameraEffect.PREVIEW;
+import static androidx.camera.core.CameraEffect.VIDEO_CAPTURE;
 import static androidx.core.util.Preconditions.checkArgument;
 
+import static java.util.Objects.requireNonNull;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -25,7 +30,11 @@
 import androidx.lifecycle.Lifecycle;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
+import java.util.Map;
 
 /**
  * Represents a collection of {@link UseCase}.
@@ -82,10 +91,17 @@
      * A builder for generating {@link UseCaseGroup}.
      */
     public static final class Builder {
+
+        // Allow-list effect targets supported by CameraX.
+        private static final List<Integer> SUPPORTED_TARGETS = Arrays.asList(
+                PREVIEW,
+                IMAGE_CAPTURE);
+
         private ViewPort mViewPort;
         private final List<UseCase> mUseCases;
         private final List<CameraEffect> mEffects;
 
+
         public Builder() {
             mUseCases = new ArrayList<>();
             mEffects = new ArrayList<>();
@@ -101,7 +117,13 @@
         }
 
         /**
-         * Adds a {@link CameraEffect} to the collection
+         * Adds a {@link CameraEffect} to the collection.
+         *
+         * <p>The value of {@link CameraEffect#getTargets()} must be unique and must be one of
+         * the supported values below:
+         * <ul>
+         * <li>{@link CameraEffect#PREVIEW}
+         * </ul>
          *
          * <p>Once added, CameraX will use the {@link CameraEffect}s to process the outputs of
          * the {@link UseCase}s.
@@ -116,6 +138,57 @@
         }
 
         /**
+         * Checks effect targets and throw {@link IllegalArgumentException}.
+         *
+         * <p>Throws exception if the effects 1) contains duplicate targets or 2) contains
+         * effects that is not in the allowlist.
+         */
+        private void checkEffectTargets() {
+            Map<Integer, CameraEffect> targetEffectMap = new HashMap<>();
+            for (CameraEffect effect : mEffects) {
+                int targets = effect.getTargets();
+                if (!SUPPORTED_TARGETS.contains(targets)) {
+                    throw new IllegalArgumentException(String.format(Locale.US,
+                            "Target %s is not in the supported list %s.",
+                            getHumanReadableTargets(targets),
+                            getHumanReadableSupportedTargets()));
+                }
+                if (targetEffectMap.containsKey(effect.getTargets())) {
+                    throw new IllegalArgumentException(String.format(Locale.US,
+                            "%s and %s contain duplicate targets %s.",
+                            requireNonNull(
+                                    targetEffectMap.get(effect.getTargets())).getClass().getName(),
+                            effect.getClass().getName(),
+                            getHumanReadableTargets(targets)));
+                }
+                targetEffectMap.put(effect.getTargets(), effect);
+            }
+        }
+
+        static String getHumanReadableSupportedTargets() {
+            List<String> targetNameList = new ArrayList<>();
+            for (Integer targets : SUPPORTED_TARGETS) {
+                targetNameList.add(getHumanReadableTargets(targets));
+            }
+            return "[" + String.join(", ", targetNameList) + "]";
+        }
+
+        static String getHumanReadableTargets(int targets) {
+            List<String> names = new ArrayList<>();
+            if ((targets & IMAGE_CAPTURE) != 0) {
+                names.add("IMAGE_CAPTURE");
+            }
+            if ((targets & PREVIEW) != 0) {
+                names.add("PREVIEW");
+            }
+
+            if ((targets & VIDEO_CAPTURE) != 0) {
+                names.add("VIDEO_CAPTURE");
+            }
+            return String.join("|", names);
+        }
+
+        /**
          * Adds {@link UseCase} to the collection.
          */
         @NonNull
@@ -130,6 +203,7 @@
         @NonNull
         public UseCaseGroup build() {
             checkArgument(!mUseCases.isEmpty(), "UseCase must not be empty.");
+            checkEffectTargets();
             return new UseCaseGroup(mViewPort, mUseCases, mEffects);
         }
     }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
index 04956c5..2918de6 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
@@ -235,7 +235,7 @@
     @AnyThread
     @Override
     public void updateTransformMatrix(@NonNull float[] output, @NonNull float[] input) {
-        System.arraycopy(input, 0, output, 0, 16);
+        System.arraycopy(mGlTransform, 0, output, 0, 16);
     }
 
     /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
index 347a8ca..efaa57f 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceProcessorNode.java
@@ -142,6 +142,11 @@
     private void sendSurfacesToProcessorWhenReady(@NonNull SettableSurface input,
             @NonNull SettableSurface output) {
         SurfaceRequest surfaceRequest = input.createSurfaceRequest(mCameraInternal);
+        setupRotationUpdates(
+                surfaceRequest,
+                output,
+                input.getMirroring(),
+                input.getRotationDegrees());
         Futures.addCallback(output.createSurfaceOutputFuture(input.getSize(), input.getCropRect(),
                         input.getRotationDegrees(), input.getMirroring()),
                 new FutureCallback<SurfaceOutput>() {
@@ -150,7 +155,6 @@
                         Preconditions.checkNotNull(surfaceOutput);
                         mSurfaceProcessor.onOutputSurface(surfaceOutput);
                         mSurfaceProcessor.onInputSurface(surfaceRequest);
-                        setupSurfaceUpdatePipeline(input, surfaceRequest, output, surfaceOutput);
                     }
 
                     @Override
@@ -163,18 +167,35 @@
                 }, mainThreadExecutor());
     }
 
-    void setupSurfaceUpdatePipeline(@NonNull SettableSurface input,
-            @NonNull SurfaceRequest inputSurfaceRequest, @NonNull SettableSurface output,
-            @NonNull SurfaceOutput surfaceOutput) {
+    /**
+     * Propagates rotation updates from the input edge to the output edge.
+     *
+     * <p>Transformation info, such as rotation and crop rect, can be updated after the
+     * connection is established. When that happens, the node should update the output
+     * transformation via e.g. {@link SurfaceRequest#updateTransformationInfo} without recreating
+     * the pipeline.
+     *
+     * <p>Currently, we only propagates the rotation. When the
+     * input edge's rotation changes, we re-calculate the delta and notify the output edge.
+     *
+     * @param inputSurfaceRequest {@link SurfaceRequest} of the input edge.
+     * @param outputSurface       {@link SettableSurface} of the output edge.
+     * @param mirrored            whether the node mirrors the buffer.
+     * @param rotatedDegrees      how much the node rotates the buffer.
+     */
+    void setupRotationUpdates(
+            @NonNull SurfaceRequest inputSurfaceRequest,
+            @NonNull SettableSurface outputSurface,
+            boolean mirrored,
+            int rotatedDegrees) {
         inputSurfaceRequest.setTransformationInfoListener(mainThreadExecutor(), info -> {
-            // Calculate rotation degrees
-            // To obtain the required rotation degrees of output surface, the rotation degrees of
-            // surfaceOutput has to be eliminated.
-            int rotationDegrees = info.getRotationDegrees() - surfaceOutput.getRotationDegrees();
-            if (input.getMirroring()) {
+            // To obtain the rotation degrees delta, the rotation performed by the node must be
+            // eliminated.
+            int rotationDegrees = info.getRotationDegrees() - rotatedDegrees;
+            if (mirrored) {
                 rotationDegrees = -rotationDegrees;
             }
-            output.setRotationDegrees(within360(rotationDegrees));
+            outputSurface.setRotationDegrees(within360(rotationDegrees));
         });
     }
 
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
index 7211a30..8bfe101 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
@@ -233,6 +233,11 @@
     }
 
     @Test
+    fun detachWithoutAttach_doesNotCrash() {
+        ImageCapture.Builder().build().onDetached()
+    }
+
+    @Test
     fun useImageReaderProvider_pipelineDisabled() {
         assertThat(
             bindImageCapture(
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 9920500..8a1c98f 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -24,6 +24,7 @@
 import android.util.Rational
 import android.util.Size
 import android.view.Surface
+import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
 import androidx.camera.core.SurfaceRequest.TransformationInfo
 import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.CameraThreadConfig
@@ -44,6 +45,7 @@
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
 import androidx.camera.testing.fakes.FakeCameraFactory
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
 import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
 import androidx.camera.testing.fakes.FakeUseCase
 import androidx.test.core.app.ApplicationProvider
@@ -51,6 +53,7 @@
 import java.util.Collections
 import java.util.concurrent.ExecutionException
 import org.junit.After
+import org.junit.Assert
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -58,8 +61,6 @@
 import org.robolectric.Shadows.shadowOf
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
-import kotlin.jvm.Throws
-import org.junit.Assert
 
 private val TEST_CAMERA_SELECTOR = CameraSelector.DEFAULT_BACK_CAMERA
 
@@ -72,27 +73,34 @@
     minSdk = Build.VERSION_CODES.LOLLIPOP
 )
 class PreviewTest {
+
     var cameraUseCaseAdapter: CameraUseCaseAdapter? = null
 
     private lateinit var appSurface: Surface
     private lateinit var appSurfaceTexture: SurfaceTexture
-    private lateinit var camera: FakeCamera
+    private lateinit var backCamera: FakeCamera
+    private lateinit var frontCamera: FakeCamera
     private lateinit var cameraXConfig: CameraXConfig
     private lateinit var context: Context
+    private lateinit var previewToDetach: Preview
 
     @Before
     @Throws(ExecutionException::class, InterruptedException::class)
     fun setUp() {
         appSurfaceTexture = SurfaceTexture(0)
         appSurface = Surface(appSurfaceTexture)
-        camera = FakeCamera()
+        backCamera = FakeCamera("back")
+        frontCamera = FakeCamera("front", null, FakeCameraInfoInternal(0, LENS_FACING_FRONT))
 
         val cameraFactoryProvider =
             CameraFactory.Provider { _: Context?, _: CameraThreadConfig?, _: CameraSelector? ->
                 val cameraFactory = FakeCameraFactory()
                 cameraFactory.insertDefaultBackCamera(
-                    camera.cameraInfoInternal.cameraId
-                ) { camera }
+                    backCamera.cameraInfoInternal.cameraId
+                ) { backCamera }
+                cameraFactory.insertDefaultFrontCamera(
+                    frontCamera.cameraInfoInternal.cameraId
+                ) { frontCamera }
                 cameraFactory
             }
         cameraXConfig = CameraXConfig.Builder.fromConfig(
@@ -111,6 +119,9 @@
             this?.removeUseCases(useCases)
         }
         cameraUseCaseAdapter = null
+        if (::previewToDetach.isInitialized) {
+            previewToDetach.onDetached()
+        }
         CameraXUtil.shutdown().get()
     }
 
@@ -252,14 +263,22 @@
     }
 
     @Test
-    fun createPreviewWithProcessor_mirroringIsTrue() {
+    fun backCameraWithProcessor_notMirrored() {
         // Arrange.
         val processor = FakeSurfaceProcessorInternal(mainThreadExecutor())
-
         // Act: create pipeline
-        val preview = createPreview(processor)
+        val preview = createPreview(processor, backCamera)
+        // Assert
+        assertThat(preview.getCameraSurface().mirroring).isFalse()
+    }
 
-        // Assert: preview is mirrored by default.
+    @Test
+    fun frontCameraWithProcessor_mirrored() {
+        // Arrange.
+        val processor = FakeSurfaceProcessorInternal(mainThreadExecutor())
+        // Act: create pipeline
+        val preview = createPreview(processor, frontCamera)
+        // Assert
         assertThat(preview.getCameraSurface().mirroring).isTrue()
     }
 
@@ -281,9 +300,6 @@
         shadowOf(getMainLooper()).idle()
         // Assert: the rotation of the SettableFuture is updated based on ROTATION_90.
         assertThat(preview.getCameraSurface().rotationDegrees).isEqualTo(180)
-
-        // Clean up
-        preview.onDetached()
     }
 
     private fun Preview.getCameraSurface(): SettableSurface {
@@ -540,21 +556,24 @@
         return Pair(surfaceRequest!!, transformationInfo!!)
     }
 
-    private fun createPreview(surfaceProcessor: SurfaceProcessorInternal? = null): Preview {
-        val preview = Preview.Builder()
+    private fun createPreview(
+        surfaceProcessor: SurfaceProcessorInternal? = null,
+        camera: FakeCamera = backCamera
+    ): Preview {
+        previewToDetach = Preview.Builder()
             .setTargetRotation(Surface.ROTATION_0)
             .build()
-        preview.processor = surfaceProcessor
-        preview.setSurfaceProvider(CameraXExecutors.directExecutor()) {}
+        previewToDetach.processor = surfaceProcessor
+        previewToDetach.setSurfaceProvider(CameraXExecutors.directExecutor()) {}
         val previewConfig = PreviewConfig(
             cameraXConfig.getUseCaseConfigFactoryProvider(null)!!.newInstance(context).getConfig(
                 UseCaseConfigFactory.CaptureType.PREVIEW,
                 ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
             )!! as OptionsBundle
         )
-        preview.onAttach(camera, null, previewConfig)
+        previewToDetach.onAttach(camera, null, previewConfig)
 
-        preview.onSuggestedResolutionUpdated(Size(640, 480))
-        return preview
+        previewToDetach.onSuggestedResolutionUpdated(Size(640, 480))
+        return previewToDetach
     }
 }
\ No newline at end of file
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/UseCaseGroupTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/UseCaseGroupTest.kt
new file mode 100644
index 0000000..a4eae79
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/UseCaseGroupTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core
+
+import android.os.Build
+import androidx.camera.core.CameraEffect.IMAGE_CAPTURE
+import androidx.camera.core.CameraEffect.PREVIEW
+import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
+import androidx.camera.core.UseCaseGroup.Builder.getHumanReadableTargets
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.testing.fakes.FakePreviewEffect
+import androidx.camera.testing.fakes.FakeSurfaceProcessor
+import androidx.camera.testing.fakes.FakeUseCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+/**
+ * Unit tests for [UseCaseGroup].
+ */
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class UseCaseGroupTest {
+
+    @Test
+    fun duplicateTargets_throwsException() {
+        // Arrange.
+        val previewEffect = FakePreviewEffect(
+            CameraXExecutors.mainThreadExecutor(),
+            FakeSurfaceProcessor(CameraXExecutors.mainThreadExecutor())
+        )
+        val builder = UseCaseGroup.Builder().addUseCase(FakeUseCase())
+            .addEffect(previewEffect)
+            .addEffect(previewEffect)
+
+        // Act.
+        var message: String? = null
+        try {
+            builder.build()
+        } catch (e: IllegalArgumentException) {
+            message = e.message
+        }
+
+        // Assert.
+        assertThat(message).isEqualTo(
+            "androidx.camera.testing.fakes.FakePreviewEffect " +
+                "and androidx.camera.testing.fakes.FakePreviewEffect " +
+                "contain duplicate targets PREVIEW."
+        )
+    }
+
+    @Test
+    fun verifyHumanReadableTargetsNames() {
+        assertThat(getHumanReadableTargets(PREVIEW)).isEqualTo("PREVIEW")
+        assertThat(getHumanReadableTargets(PREVIEW or VIDEO_CAPTURE))
+            .isEqualTo("PREVIEW|VIDEO_CAPTURE")
+        assertThat(getHumanReadableTargets(PREVIEW or VIDEO_CAPTURE or IMAGE_CAPTURE))
+            .isEqualTo("IMAGE_CAPTURE|PREVIEW|VIDEO_CAPTURE")
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
index 6f44748..2ea4195 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
@@ -100,6 +100,27 @@
     }
 
     @Test
+    fun updateMatrix_containsOpenGlFlipping() {
+        // Arrange.
+        val surfaceOut = createFakeSurfaceOutputImpl()
+        val input = FloatArray(16).also {
+            android.opengl.Matrix.setIdentityM(it, 0)
+        }
+
+        // Act.
+        val result = FloatArray(16)
+        surfaceOut.updateTransformMatrix(result, input)
+
+        // Assert: the result contains the flipping for OpenGL.
+        val expected = FloatArray(16).also {
+            android.opengl.Matrix.setIdentityM(it, 0)
+            android.opengl.Matrix.translateM(it, 0, 0f, 1f, 0f)
+            android.opengl.Matrix.scaleM(it, 0, 1f, -1f, 1f)
+        }
+        assertThat(result).usingTolerance(1E-4).containsExactly(expected)
+    }
+
+    @Test
     fun closedSurface_noLongerReceivesCloseRequest() {
         // Arrange.
         val surfaceOutImpl = createFakeSurfaceOutputImpl()
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
index d037ab4..db01315 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
@@ -47,6 +47,7 @@
 import androidx.core.util.Consumer
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
@@ -341,6 +342,7 @@
         file.delete()
     }
 
+    @FlakyTest(bugId = 259294631)
     @Test
     fun canRecordToFile_rightAfterPreviousRecordingStopped() {
         // Arrange.
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
index f0db38c..7a17ca1 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -36,7 +36,6 @@
 import androidx.annotation.OptIn
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.Camera2Config
-import androidx.camera.camera2.interop.Camera2Interop
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop
 import androidx.camera.camera2.pipe.integration.CameraPipeConfig
 import androidx.camera.core.AspectRatio
@@ -66,6 +65,7 @@
 import androidx.camera.core.impl.SessionProcessor
 import androidx.camera.core.impl.utils.CameraOrientationUtil
 import androidx.camera.core.impl.utils.Exif
+import androidx.camera.integration.core.util.CameraPipeUtil
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.CameraPipeConfigTestRule
 import androidx.camera.testing.CameraUtil
@@ -547,8 +547,13 @@
         assertThat(error).isEqualTo(ImageCapture.ERROR_FILE_IO)
     }
 
+    @kotlin.OptIn(
+        androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop::class
+    )
     @Test
-    @OptIn(markerClass = [ExperimentalCamera2Interop::class])
+    @OptIn(
+        markerClass = [ExperimentalCamera2Interop::class]
+    )
     fun camera2InteropCaptureSessionCallbacks() = runBlocking {
         val stillCaptureCount = AtomicInteger(0)
         val captureCallback = object : CaptureCallback() {
@@ -566,7 +571,8 @@
             }
         }
         val builder = ImageCapture.Builder()
-        Camera2Interop.Extender(builder).setSessionCaptureCallback(captureCallback)
+        CameraPipeUtil.setCameraCaptureSessionCallback(implName, builder, captureCallback)
+
         val useCase = builder.build()
 
         withContext(Dispatchers.Main) {
@@ -1380,7 +1386,8 @@
 
         val imageCapture = builder
             .setSupportedResolutions(
-                listOf(android.util.Pair(ImageFormat.YUV_420_888, arrayOf(Size(640, 480)))))
+                listOf(android.util.Pair(ImageFormat.YUV_420_888, arrayOf(Size(640, 480))))
+            )
             .build()
 
         val preview = Preview.Builder().build()
@@ -1391,7 +1398,8 @@
             val cameraSelector =
                 getCameraSelectorWithSessionProcessor(BACK_SELECTOR, sessionProcessor)
             camera = cameraProvider.bindToLifecycle(
-                fakeLifecycleOwner, cameraSelector, imageCapture, preview)
+                fakeLifecycleOwner, cameraSelector, imageCapture, preview
+            )
         }
 
         val callback = FakeImageCaptureCallback(capturesCount = 1)
@@ -1426,7 +1434,8 @@
 
         val imageCapture = builder
             .setSupportedResolutions(
-                listOf(android.util.Pair(ImageFormat.JPEG, arrayOf(Size(640, 480)))))
+                listOf(android.util.Pair(ImageFormat.JPEG, arrayOf(Size(640, 480))))
+            )
             .build()
 
         val preview = Preview.Builder().build()
@@ -1436,7 +1445,8 @@
             val cameraSelector =
                 getCameraSelectorWithSessionProcessor(BACK_SELECTOR, sessionProcessor)
             cameraProvider.bindToLifecycle(
-                fakeLifecycleOwner, cameraSelector, imageCapture, preview)
+                fakeLifecycleOwner, cameraSelector, imageCapture, preview
+            )
         }
 
         val callback = FakeImageCaptureCallback(capturesCount = 1)
@@ -1536,13 +1546,18 @@
         simpleCaptureProcessor.close()
     }
 
+    @kotlin.OptIn(
+        androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop::class
+    )
     @Test
     fun unbindPreview_imageCapturingShouldSuccess() = runBlocking {
         // Arrange.
         val imageCapture = ImageCapture.Builder().build()
         val previewStreamReceived = CompletableDeferred<Boolean>()
         val preview = Preview.Builder().also {
-            Camera2Interop.Extender(it).setSessionCaptureCallback(
+            CameraPipeUtil.setCameraCaptureSessionCallback(
+                implName,
+                it,
                 object : CaptureCallback() {
                     override fun onCaptureCompleted(
                         session: CameraCaptureSession,
@@ -1551,13 +1566,13 @@
                     ) {
                         previewStreamReceived.complete(true)
                     }
-                }
-            )
+                })
         }.build()
         withContext(Dispatchers.Main) {
             preview.setSurfaceProvider(SurfaceTextureProvider.createSurfaceTextureProvider())
             cameraProvider.bindToLifecycle(
-                fakeLifecycleOwner, BACK_SELECTOR, imageCapture, preview)
+                fakeLifecycleOwner, BACK_SELECTOR, imageCapture, preview
+            )
         }
         assertWithMessage("Preview doesn't start").that(
             previewStreamReceived.awaitWithTimeoutOrNull()
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
index 81b213a..bb0b66d 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
@@ -20,7 +20,6 @@
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.TotalCaptureResult
 import androidx.camera.camera2.Camera2Config
-import androidx.camera.camera2.interop.Camera2Interop
 import androidx.camera.camera2.pipe.integration.CameraPipeConfig
 import androidx.camera.core.Camera
 import androidx.camera.core.CameraSelector
@@ -31,6 +30,7 @@
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.ImageProxy
 import androidx.camera.core.Preview
+import androidx.camera.integration.core.util.CameraPipeUtil
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.CameraPipeConfigTestRule
 import androidx.camera.testing.CameraUtil
@@ -444,7 +444,7 @@
         return Preview.Builder()
             .setTargetName("Preview").also {
                 monitor?.let { monitor ->
-                    Camera2Interop.Extender(it).setSessionCaptureCallback(monitor)
+                    CameraPipeUtil.setCameraCaptureSessionCallback(implName, it, monitor)
                 }
             }.build()
     }
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/CameraPipeUtil.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/CameraPipeUtil.kt
new file mode 100644
index 0000000..e21e129
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/CameraPipeUtil.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.core.util
+
+import android.hardware.camera2.CameraCaptureSession
+import androidx.annotation.OptIn
+import androidx.camera.camera2.interop.Camera2Interop
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig
+import androidx.camera.core.ExtendableBuilder
+
+object CameraPipeUtil {
+
+    @kotlin.OptIn(
+        androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop::class
+    )
+    @OptIn(
+        markerClass = [ExperimentalCamera2Interop::class]
+    )
+    @JvmStatic
+    fun <T> setCameraCaptureSessionCallback(
+        implName: String,
+        builder: ExtendableBuilder<T>,
+        captureCallback: CameraCaptureSession.CaptureCallback
+    ) {
+        if (implName == CameraPipeConfig::class.simpleName) {
+            androidx.camera.camera2.pipe.integration.interop.Camera2Interop.Extender(builder)
+                .setSessionCaptureCallback(captureCallback)
+        } else {
+            Camera2Interop.Extender(builder).setSessionCaptureCallback(captureCallback)
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/viewpager/CameraFragment.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/viewpager/CameraFragment.kt
index 964d3d8..d4b972fe 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/viewpager/CameraFragment.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/viewpager/CameraFragment.kt
@@ -99,7 +99,6 @@
                     removeExtra(KEY_CAMERA_IMPLEMENTATION)
                     removeExtra(KEY_CAMERA_IMPLEMENTATION_NO_HISTORY)
                 }
-                cameraImpl = null
             }
         }
 
@@ -182,6 +181,9 @@
      * content can not be got.
      */
     @OptIn(ExperimentalCamera2Interop::class)
+    @kotlin.OptIn(
+        androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop::class
+    )
     private fun Preview.Builder.addCaptureCompletedCallback() {
         val captureCallback = object : CameraCaptureSession.CaptureCallback() {
             override fun onCaptureCompleted(
@@ -197,6 +199,12 @@
             }
         }
 
+        if (cameraImpl.equals(CAMERA_PIPE_IMPLEMENTATION_OPTION)) {
+            androidx.camera.camera2.pipe.integration.interop.Camera2Interop.Extender(this)
+                .setSessionCaptureCallback(captureCallback)
+        } else {
+            Camera2Interop.Extender(this).setSessionCaptureCallback(captureCallback)
+        }
         Camera2Interop.Extender(this).setSessionCaptureCallback(captureCallback)
     }
 
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml
index 7d3d9cb..11e29a5 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml
@@ -22,8 +22,8 @@
     <string name="exit_action_title" msgid="9086586388884500731">"Exit"</string>
     <string name="refresh_action_title" msgid="3674260822403151377">"Refresh"</string>
     <string name="close_action_title" msgid="2661907510124308560">"Close"</string>
-    <string name="grant_access_action_title" msgid="4033140828804350723">"Grant access"</string>
-    <string name="enable_location_action_title" msgid="8181862247222797044">"Enable location"</string>
+    <string name="grant_access_action_title" msgid="4033140828804350723">"Grant Access"</string>
+    <string name="enable_location_action_title" msgid="8181862247222797044">"Enable Location"</string>
     <string name="cancel_action_title" msgid="1149738685397349236">"Cancel"</string>
     <string name="stop_action_title" msgid="1187619482795416314">"Stop"</string>
     <string name="more_action_title" msgid="1039516575011403837">"More"</string>
@@ -31,8 +31,8 @@
     <string name="primary_action_title" msgid="7042003552215710683">"Primary"</string>
     <string name="options_action_title" msgid="1168121856107932984">"Options"</string>
     <string name="search_action_title" msgid="3483459674263446335">"Search"</string>
-    <string name="checked_action_title" msgid="906023941445896399">"Ticked"</string>
-    <string name="unchecked_action_title" msgid="802503415474307811">"Unticked"</string>
+    <string name="checked_action_title" msgid="906023941445896399">"Checked"</string>
+    <string name="unchecked_action_title" msgid="802503415474307811">"Unchecked"</string>
     <string name="on_action_title" msgid="4129601573763429611">"On"</string>
     <string name="off_action_title" msgid="8669201170189204848">"Off"</string>
     <string name="settings_action_title" msgid="8616900063253887861">"Settings"</string>
@@ -42,11 +42,11 @@
     <string name="throw_action_title" msgid="7163710562670220163">"Throw"</string>
     <string name="commute_action_title" msgid="2585755255290185096">"Commute"</string>
     <string name="sign_out_action_title" msgid="1653943000866713010">"Sign out"</string>
-    <string name="try_anyway_action_title" msgid="7384500054249311718">"Try anyway"</string>
+    <string name="try_anyway_action_title" msgid="7384500054249311718">"Try Anyway"</string>
     <string name="yes_action_title" msgid="5507096013762092189">"Yes"</string>
     <string name="no_action_title" msgid="1452124604210014010">"No"</string>
-    <string name="disable_all_rows" msgid="3003225080532928046">"Disable all rows"</string>
-    <string name="enable_all_rows" msgid="7274285275711872091">"Enable all rows"</string>
+    <string name="disable_all_rows" msgid="3003225080532928046">"Disable All Rows"</string>
+    <string name="enable_all_rows" msgid="7274285275711872091">"Enable All Rows"</string>
     <string name="bug_reported_toast_msg" msgid="2487119172744644317">"Bug reported!"</string>
     <string name="zoomed_in_toast_msg" msgid="8915301497303842649">"Zoomed in"</string>
     <string name="zoomed_out_toast_msg" msgid="6260981223227212493">"Zoomed out"</string>
@@ -54,21 +54,21 @@
     <string name="primary_toast_msg" msgid="7153771322662005447">"Primary button pressed"</string>
     <string name="search_toast_msg" msgid="7826530065407699347">"Search button pressed"</string>
     <string name="options_toast_msg" msgid="2146223786877557730">"Options button pressed"</string>
-    <string name="favorite_toast_msg" msgid="522064494016370117">"Favourite!"</string>
-    <string name="not_favorite_toast_msg" msgid="6831181108681007428">"Not a favourite!"</string>
-    <string name="nav_requested_toast_msg" msgid="6696525973145493908">"Navigation requested"</string>
+    <string name="favorite_toast_msg" msgid="522064494016370117">"Favorite!"</string>
+    <string name="not_favorite_toast_msg" msgid="6831181108681007428">"Not a favorite!"</string>
+    <string name="nav_requested_toast_msg" msgid="6696525973145493908">"Navigation Requested"</string>
     <string name="selected_route_toast_msg" msgid="3149189677200086656">"Selected route"</string>
     <string name="visible_routes_toast_msg" msgid="7065558153736024203">"Visible routes"</string>
     <string name="second_item_toast_msg" msgid="7210054709419608215">"Clicked second item"</string>
-    <string name="third_item_checked_toast_msg" msgid="3022450599567347361">"Third item ticked"</string>
-    <string name="fifth_item_checked_toast_msg" msgid="1627599668504718594">"Fifth item ticked"</string>
+    <string name="third_item_checked_toast_msg" msgid="3022450599567347361">"Third item checked"</string>
+    <string name="fifth_item_checked_toast_msg" msgid="1627599668504718594">"Fifth item checked"</string>
     <string name="sixth_item_toast_msg" msgid="6117028866385793707">"Clicked sixth item"</string>
     <string name="settings_toast_msg" msgid="7697794473002342727">"Clicked Settings"</string>
     <string name="parked_toast_msg" msgid="2532422265890824446">"Parked action"</string>
     <string name="more_toast_msg" msgid="5938288138225509885">"Clicked More"</string>
     <string name="commute_toast_msg" msgid="4112684360647638688">"Commute button pressed"</string>
-    <string name="grant_location_permission_toast_msg" msgid="268046297444808010">"Grant location permission to see current location"</string>
-    <string name="sign_in_with_google_toast_msg" msgid="5720947549233124775">"Sign in with Google starts here"</string>
+    <string name="grant_location_permission_toast_msg" msgid="268046297444808010">"Grant location Permission to see current location"</string>
+    <string name="sign_in_with_google_toast_msg" msgid="5720947549233124775">"Sign-in with Google starts here"</string>
     <string name="changes_selection_to_index_toast_msg_prefix" msgid="957766225794389167">"Changed selection to index"</string>
     <string name="yes_action_toast_msg" msgid="6216215197177241247">"Yes button pressed!"</string>
     <string name="no_action_toast_msg" msgid="6165492423831023809">"No button pressed!"</string>
@@ -82,61 +82,61 @@
     <string name="address" msgid="9010635942573581302">"Address"</string>
     <string name="phone" msgid="2504766809811627577">"Phone"</string>
     <string name="fail_start_nav" msgid="6921321606009212189">"Failure starting navigation"</string>
-    <string name="fail_start_dialer" msgid="1471602619507306261">"Failure starting dialler"</string>
-    <string name="car_hardware_demo_title" msgid="3679106197233262689">"Car hardware demo"</string>
-    <string name="car_hardware_info" msgid="1244783247616395012">"Car hardware information"</string>
-    <string name="model_info" msgid="494224423025683030">"Model information"</string>
-    <string name="no_model_permission" msgid="5333629877014978947">"No model permission"</string>
+    <string name="fail_start_dialer" msgid="1471602619507306261">"Failure starting dialer"</string>
+    <string name="car_hardware_demo_title" msgid="3679106197233262689">"Car Hardware Demo"</string>
+    <string name="car_hardware_info" msgid="1244783247616395012">"Car Hardware Information"</string>
+    <string name="model_info" msgid="494224423025683030">"Model Information"</string>
+    <string name="no_model_permission" msgid="5333629877014978947">"No Model Permission"</string>
     <string name="manufacturer_unavailable" msgid="4978995415869838056">"Manufacturer unavailable"</string>
     <string name="model_unavailable" msgid="4075463010215406573">"Model unavailable"</string>
     <string name="year_unavailable" msgid="994338773299644607">"Year unavailable"</string>
-    <string name="energy_profile" msgid="81415433590192158">"Energy profile"</string>
-    <string name="no_energy_profile_permission" msgid="4662285713731308888">"No energy profile permission"</string>
-    <string name="fuel_types" msgid="6811375173343218212">"Fuel types"</string>
+    <string name="energy_profile" msgid="81415433590192158">"Energy Profile"</string>
+    <string name="no_energy_profile_permission" msgid="4662285713731308888">"No Energy Profile Permission"</string>
+    <string name="fuel_types" msgid="6811375173343218212">"Fuel Types"</string>
     <string name="unavailable" msgid="3636401138255192934">"Unavailable"</string>
-    <string name="ev_connector_types" msgid="735458637011996125">"EV connector types"</string>
+    <string name="ev_connector_types" msgid="735458637011996125">"EV Connector Types"</string>
     <string name="example_title" msgid="530257630320010494">"Example %d"</string>
-    <string name="example_1_text" msgid="8456567953748293512">"This text has a red colour"</string>
-    <string name="example_2_text" msgid="718820705318661440">"This text has a green colour"</string>
-    <string name="example_3_text" msgid="977269832109695627">"This text has a blue colour"</string>
-    <string name="example_4_text" msgid="2043547015979437373">"This text has a yellow colour"</string>
-    <string name="example_5_text" msgid="8828804968749423500">"This text uses the primary colour"</string>
-    <string name="example_6_text" msgid="7991523168517599600">"This text uses the secondary colour"</string>
-    <string name="color_demo" msgid="1822427636476178993">"Colour demo"</string>
-    <string name="list_limit" msgid="3023536401535417286">"List limit"</string>
-    <string name="grid_limit" msgid="1350116012893549206">"Grid limit"</string>
-    <string name="pane_limit" msgid="981518409516855230">"Pane limit"</string>
-    <string name="place_list_limit" msgid="6785181191763056582">"Place list limit"</string>
-    <string name="route_list_limit" msgid="505793441615134116">"Route list limit"</string>
-    <string name="content_limits" msgid="5726880972110281095">"Content limits"</string>
-    <string name="content_limits_demo_title" msgid="3207211638386727610">"Content limits demo"</string>
+    <string name="example_1_text" msgid="8456567953748293512">"This text has a red color"</string>
+    <string name="example_2_text" msgid="718820705318661440">"This text has a green color"</string>
+    <string name="example_3_text" msgid="977269832109695627">"This text has a blue color"</string>
+    <string name="example_4_text" msgid="2043547015979437373">"This text has a yellow color"</string>
+    <string name="example_5_text" msgid="8828804968749423500">"This text uses the primary color"</string>
+    <string name="example_6_text" msgid="7991523168517599600">"This text uses the secondary color"</string>
+    <string name="color_demo" msgid="1822427636476178993">"Color Demo"</string>
+    <string name="list_limit" msgid="3023536401535417286">"List Limit"</string>
+    <string name="grid_limit" msgid="1350116012893549206">"Grid Limit"</string>
+    <string name="pane_limit" msgid="981518409516855230">"Pane Limit"</string>
+    <string name="place_list_limit" msgid="6785181191763056582">"Place List Limit"</string>
+    <string name="route_list_limit" msgid="505793441615134116">"Route List Limit"</string>
+    <string name="content_limits" msgid="5726880972110281095">"Content Limits"</string>
+    <string name="content_limits_demo_title" msgid="3207211638386727610">"Content Limits Demo"</string>
     <string name="finish_app_msg" msgid="8354334557053141891">"This will finish the app, and when you return it will pre-seed a permission screen"</string>
-    <string name="finish_app_title" msgid="9013328479438745074">"Finish app demo"</string>
-    <string name="finish_app_demo_title" msgid="8223819062053448384">"Pre-seed the permission screen on next run demo"</string>
-    <string name="preseed_permission_app_title" msgid="182847662545676962">"Pre-seed permission app demo"</string>
-    <string name="preseed_permission_demo_title" msgid="5476541421753978071">"Pre-seed the permission screen on next run demo"</string>
-    <string name="loading_demo_title" msgid="1086529475809143517">"Loading demo"</string>
-    <string name="loading_demo_row_title" msgid="8933049915126088142">"Loading complete!"</string>
+    <string name="finish_app_title" msgid="9013328479438745074">"Finish App Demo"</string>
+    <string name="finish_app_demo_title" msgid="8223819062053448384">"Pre-seed the Permission Screen on next run Demo"</string>
+    <string name="preseed_permission_app_title" msgid="182847662545676962">"Pre-seed permission App Demo"</string>
+    <string name="preseed_permission_demo_title" msgid="5476541421753978071">"Pre-seed the Permission Screen on next run Demo"</string>
+    <string name="loading_demo_title" msgid="1086529475809143517">"Loading Demo"</string>
+    <string name="loading_demo_row_title" msgid="8933049915126088142">"Loading Complete!"</string>
     <string name="pop_to_root" msgid="2078277386355064198">"Pop to root"</string>
-    <string name="pop_to_marker" msgid="5007078308762725207">"Pop to misc demo marker"</string>
+    <string name="pop_to_marker" msgid="5007078308762725207">"Pop to Misc Demo Marker"</string>
     <string name="push_stack" msgid="2433062141810168976">"Push further in stack"</string>
-    <string name="pop_to_prefix" msgid="4288884615669751608">"Pop to"</string>
-    <string name="pop_to_title" msgid="3924696281273379455">"PopTo demo"</string>
-    <string name="package_not_found_error_msg" msgid="7525619456883627939">"Package not found."</string>
+    <string name="pop_to_prefix" msgid="4288884615669751608">"Pop To"</string>
+    <string name="pop_to_title" msgid="3924696281273379455">"PopTo Demo"</string>
+    <string name="package_not_found_error_msg" msgid="7525619456883627939">"Package Not found."</string>
     <string name="permissions_granted_msg" msgid="2348556088141992714">"All permissions have been granted. Please revoke permissions from Settings."</string>
     <string name="needs_access_msg_prefix" msgid="2204136858798832382">"The app needs access to the following permissions:\n"</string>
-    <string name="phone_screen_permission_msg" msgid="3599815596923367256">"Grant permission on the phone screen"</string>
-    <string name="enable_location_permission_on_device_msg" msgid="472752487966156897">"Enable location permissions on device"</string>
+    <string name="phone_screen_permission_msg" msgid="3599815596923367256">"Grant Permission on the phone screen"</string>
+    <string name="enable_location_permission_on_device_msg" msgid="472752487966156897">"Enable Location Permissions on device"</string>
     <string name="enable_location_permission_on_phone_msg" msgid="5082615523959139121">"Enable location on the phone screen"</string>
-    <string name="required_permissions_title" msgid="5351791879153568211">"Required permissions"</string>
-    <string name="request_permissions_title" msgid="7456426341142412300">"Request permission demo"</string>
-    <string name="cancel_reservation_title" msgid="1374986823057959608">"Cancel reservation screen"</string>
-    <string name="reservation_cancelled_msg" msgid="6334213670275547875">"Reservation cancelled"</string>
+    <string name="required_permissions_title" msgid="5351791879153568211">"Required Permissions"</string>
+    <string name="request_permissions_title" msgid="7456426341142412300">"Request Permission Demo"</string>
+    <string name="cancel_reservation_title" msgid="1374986823057959608">"Cancel Reservation Screen"</string>
+    <string name="reservation_cancelled_msg" msgid="6334213670275547875">"Reservation canceled"</string>
     <string name="result_demo_title" msgid="3900525190662148290">"Result demo"</string>
     <string name="not_started_for_result_msg" msgid="7498800528148447270">"This app was not started for result"</string>
     <string name="started_for_result_msg" msgid="4225260243713833974">"This app was called for result from %s. Please select the result to send back to the caller"</string>
     <string name="arrived_exclamation_msg" msgid="9132265698563096988">"Arrived!"</string>
-    <string name="travel_est_trip_text" msgid="5134365408383171144">"Pick up Alice"</string>
+    <string name="travel_est_trip_text" msgid="5134365408383171144">"Pick Up Alice"</string>
     <string name="send_notification_title" msgid="4731688444696028193">"Send a notification"</string>
     <string name="start_notifications_title" msgid="8699668027543530460">"Start notifications"</string>
     <string name="stop_notifications_title" msgid="3703892710275206239">"Stop notifications"</string>
@@ -148,99 +148,99 @@
     <string name="high_importance" msgid="5009120714837304882">"High"</string>
     <string name="low_importance" msgid="4721161314404294033">"Low"</string>
     <string name="unknown_importance" msgid="909001735933359216">"Unknown"</string>
-    <string name="notification_demo" msgid="1819496937832036387">"Notification demo"</string>
-    <string name="misc_demo_title" msgid="7399959062407349380">"Misc demos"</string>
-    <string name="navigating_demo_title" msgid="7454579665387386476">"Navigating demo"</string>
-    <string name="arrived_demo_title" msgid="6708013121387053838">"Arrived demo"</string>
-    <string name="junction_image_demo_title" msgid="3302979708776502314">"Junction image demo"</string>
-    <string name="nav_template_demos_title" msgid="8215835368932160866">"Navigation template demos"</string>
-    <string name="map_template_pane_demo_title" msgid="4849450903277412004">"Map template with pane demo"</string>
-    <string name="map_template_list_demo_title" msgid="1473810899303903185">"Map template with list demo"</string>
-    <string name="start_notification_title" msgid="2208831088632818681">"Start notification"</string>
-    <string name="stop_notification_title" msgid="3709643750540881176">"Stop notification"</string>
-    <string name="nav_notification_demo_title" msgid="4448683262984308442">"Navigation notification demo"</string>
-    <string name="go_straight" msgid="2301747728609198718">"Go straight"</string>
-    <string name="turn_right" msgid="4710562732720109969">"Turn right"</string>
+    <string name="notification_demo" msgid="1819496937832036387">"Notification Demo"</string>
+    <string name="misc_demo_title" msgid="7399959062407349380">"Misc Demos"</string>
+    <string name="navigating_demo_title" msgid="7454579665387386476">"Navigating Demo"</string>
+    <string name="arrived_demo_title" msgid="6708013121387053838">"Arrived Demo"</string>
+    <string name="junction_image_demo_title" msgid="3302979708776502314">"Junction Image Demo"</string>
+    <string name="nav_template_demos_title" msgid="8215835368932160866">"Navigation Template Demos"</string>
+    <string name="map_template_pane_demo_title" msgid="4849450903277412004">"Map Template with Pane Demo"</string>
+    <string name="map_template_list_demo_title" msgid="1473810899303903185">"Map Template with List Demo"</string>
+    <string name="start_notification_title" msgid="2208831088632818681">"Start Notification"</string>
+    <string name="stop_notification_title" msgid="3709643750540881176">"Stop Notification"</string>
+    <string name="nav_notification_demo_title" msgid="4448683262984308442">"Navigation Notification Demo"</string>
+    <string name="go_straight" msgid="2301747728609198718">"Go Straight"</string>
+    <string name="turn_right" msgid="4710562732720109969">"Turn Right"</string>
     <string name="take_520" msgid="3804796387195842741">"Take 520"</string>
-    <string name="gas_station" msgid="1203313937444666161">"Petrol station"</string>
+    <string name="gas_station" msgid="1203313937444666161">"Gas Station"</string>
     <string name="short_route" msgid="4831864276538141265">"Short route"</string>
     <string name="less_busy" msgid="310625272281710983">"Less busy"</string>
-    <string name="hov_friendly" msgid="6956152104754594971">"HOV-friendly"</string>
+    <string name="hov_friendly" msgid="6956152104754594971">"HOV friendly"</string>
     <string name="long_route" msgid="4737969235741057506">"Long route"</string>
     <string name="continue_start_nav" msgid="6231797535084469163">"Continue to start navigation"</string>
     <string name="continue_route" msgid="5172258139245088080">"Continue to route"</string>
     <string name="routes_title" msgid="7799772149932075357">"Routes"</string>
-    <string name="place_list_nav_template_demo_title" msgid="8019588508812955290">"Place list navigation template demo"</string>
-    <string name="route_preview_template_demo_title" msgid="7878704357953167555">"Route preview template demo"</string>
-    <string name="notification_template_demo_title" msgid="5076051497316030274">"Notification template demo"</string>
-    <string name="nav_map_template_demo_title" msgid="344985380763975398">"Navigation template with map-only demo"</string>
-    <string name="nav_demos_title" msgid="72781206086461004">"Navigation demos"</string>
+    <string name="place_list_nav_template_demo_title" msgid="8019588508812955290">"Place List Navigation Template Demo"</string>
+    <string name="route_preview_template_demo_title" msgid="7878704357953167555">"Route Preview Template Demo"</string>
+    <string name="notification_template_demo_title" msgid="5076051497316030274">"Notification Template Demo"</string>
+    <string name="nav_map_template_demo_title" msgid="344985380763975398">"Navigation Template with map only Demo"</string>
+    <string name="nav_demos_title" msgid="72781206086461004">"Navigation Demos"</string>
     <string name="navigation_alert_title" msgid="8306554249264200848">"Still a speed trap?"</string>
     <string name="navigation_alert_subtitle" msgid="3331130131492672264">"Reported 10m ago"</string>
-    <string name="no_toll_card_permission" msgid="6789073114449712090">"No TollCard permission."</string>
-    <string name="no_energy_level_permission" msgid="1684773185095107825">"No EnergyLevel permission."</string>
-    <string name="no_speed_permission" msgid="5812532480922675390">"No speed permission."</string>
-    <string name="no_mileage_permission" msgid="4074779840599589847">"No mileage permission."</string>
-    <string name="no_ev_status_permission" msgid="933075402821938973">"No EV status permission."</string>
-    <string name="no_accelerometer_permission" msgid="896914448469117234">"No accelerometer permission."</string>
-    <string name="no_gyroscope_permission" msgid="665293140266771569">"No gyroscope permission."</string>
-    <string name="no_compass_permission" msgid="5162304489577567125">"No compass permission."</string>
-    <string name="no_car_hardware_location" msgid="3505517472938045093">"No CarHardwareLocation permission."</string>
-    <string name="fetch_toll_info" msgid="6864627977128179834">"Fetching toll information."</string>
-    <string name="fetch_energy_level" msgid="1773415471137542832">"Fetching energy level."</string>
-    <string name="fetch_speed" msgid="7333830984597189627">"Fetching speed."</string>
-    <string name="fetch_mileage" msgid="7490131687788025123">"Fetching mileage."</string>
+    <string name="no_toll_card_permission" msgid="6789073114449712090">"No TollCard Permission."</string>
+    <string name="no_energy_level_permission" msgid="1684773185095107825">"No EnergyLevel Permission."</string>
+    <string name="no_speed_permission" msgid="5812532480922675390">"No Speed Permission."</string>
+    <string name="no_mileage_permission" msgid="4074779840599589847">"No Mileage Permission."</string>
+    <string name="no_ev_status_permission" msgid="933075402821938973">"No EV status Permission."</string>
+    <string name="no_accelerometer_permission" msgid="896914448469117234">"No Accelerometer Permission."</string>
+    <string name="no_gyroscope_permission" msgid="665293140266771569">"No Gyroscope Permission."</string>
+    <string name="no_compass_permission" msgid="5162304489577567125">"No Compass Permission."</string>
+    <string name="no_car_hardware_location" msgid="3505517472938045093">"No CarHardwareLocation Permission."</string>
+    <string name="fetch_toll_info" msgid="6864627977128179834">"Fetching Toll information."</string>
+    <string name="fetch_energy_level" msgid="1773415471137542832">"Fetching Energy Level."</string>
+    <string name="fetch_speed" msgid="7333830984597189627">"Fetching Speed."</string>
+    <string name="fetch_mileage" msgid="7490131687788025123">"Fetching Mileage."</string>
     <string name="fetch_ev_status" msgid="2798910410830567052">"Fetching EV status."</string>
-    <string name="fetch_accelerometer" msgid="697750041126810911">"Fetching accelerometer."</string>
-    <string name="fetch_gyroscope" msgid="7153155318827188539">"Fetching gyroscope."</string>
-    <string name="fetch_compass" msgid="7316188117590056717">"Fetching compass."</string>
-    <string name="fetch_location" msgid="5015066922035852615">"Fetching location."</string>
+    <string name="fetch_accelerometer" msgid="697750041126810911">"Fetching Accelerometer."</string>
+    <string name="fetch_gyroscope" msgid="7153155318827188539">"Fetching Gyroscope."</string>
+    <string name="fetch_compass" msgid="7316188117590056717">"Fetching Compass."</string>
+    <string name="fetch_location" msgid="5015066922035852615">"Fetching Location."</string>
     <string name="toll_card_state" msgid="4430544885695162226">"Toll card state"</string>
     <string name="low_energy" msgid="3462774027012877028">"Low energy"</string>
     <string name="range" msgid="8744960568263400641">"Range"</string>
     <string name="fuel" msgid="4253578650127250651">"Fuel"</string>
     <string name="battery" msgid="2183623637331546820">"Battery"</string>
-    <string name="display_speed" msgid="9161318805331348165">"Display speed"</string>
-    <string name="raw_speed" msgid="7295910214088983967">"Raw speed"</string>
+    <string name="display_speed" msgid="9161318805331348165">"Display Speed"</string>
+    <string name="raw_speed" msgid="7295910214088983967">"Raw Speed"</string>
     <string name="unit" msgid="7697521583928135171">"Unit"</string>
     <string name="odometer" msgid="3925174645651546591">"Odometer"</string>
-    <string name="ev_connected" msgid="2277845607662494696">"EV charging port connected"</string>
-    <string name="ev_open" msgid="4916704450914519643">"EV charging port open"</string>
+    <string name="ev_connected" msgid="2277845607662494696">"Ev Charge Port Connected"</string>
+    <string name="ev_open" msgid="4916704450914519643">"Ev Charge Port Open"</string>
     <string name="accelerometer" msgid="2084026313768299185">"Accelerometer"</string>
     <string name="gyroscope" msgid="3428075828014504651">"Gyroscope"</string>
     <string name="compass" msgid="7037367764762441245">"Compass"</string>
-    <string name="car_hardware_location" msgid="5826128477363068617">"Car hardware location"</string>
+    <string name="car_hardware_location" msgid="5826128477363068617">"Car Hardware Location"</string>
     <string name="non_actionable" msgid="8999757911111188784">"Non-actionable"</string>
-    <string name="second_item" msgid="5245792503030812493">"Second item"</string>
-    <string name="third_item" msgid="334088179008716411">"Third item"</string>
-    <string name="fourth_item" msgid="1153409687260543158">"Fourth item"</string>
-    <string name="fifth_item" msgid="295284272719956932">"Fifth item has a long title set"</string>
-    <string name="sixth_item" msgid="3880321601391343607">"Sixth item has a long title set"</string>
-    <string name="grid_template_demo_title" msgid="6159115661928982245">"Grid template demo"</string>
-    <string name="parked_only_title" msgid="3190603222397552672">"Parked only title"</string>
-    <string name="parked_only_text" msgid="8720610556452272916">"More parked only text."</string>
+    <string name="second_item" msgid="5245792503030812493">"Second Item"</string>
+    <string name="third_item" msgid="334088179008716411">"Third Item"</string>
+    <string name="fourth_item" msgid="1153409687260543158">"Fourth Item"</string>
+    <string name="fifth_item" msgid="295284272719956932">"Fifth Item has a long title set"</string>
+    <string name="sixth_item" msgid="3880321601391343607">"Sixth Item has a long title set"</string>
+    <string name="grid_template_demo_title" msgid="6159115661928982245">"Grid Template Demo"</string>
+    <string name="parked_only_title" msgid="3190603222397552672">"Parked Only Title"</string>
+    <string name="parked_only_text" msgid="8720610556452272916">"More Parked only text."</string>
     <string name="clicked_row_prefix" msgid="9068303427922069941">"Clicked row"</string>
     <string name="first_line_text" msgid="6648055806656590336">"First line of text"</string>
     <string name="second_line_text" msgid="38664490158836864">"Second line of text"</string>
-    <string name="long_line_text" msgid="8082962600953333087">"This subtext can fully display in unrestricted mode (e.g. parking mode, restricted low-speed mode). But this will truncate to only two lines while in Restricted mode (e.g. Driving mode). For testing purposes, this subtext is super super super super super long"</string>
+    <string name="long_line_text" msgid="8082962600953333087">"This subtext can fully display in unrestricted mode (ex. parking mode, restricted low speed mode). But this will truncate to only two lines while in restricted mode (ex. driving mode). For testing purposes, this subtext is super super super super super long"</string>
     <string name="title_prefix" msgid="3991742709199357049">"Title"</string>
-    <string name="list_template_demo_title" msgid="1740208242737246151">"List template demo"</string>
-    <string name="long_msg_template_demo_title" msgid="1793748562161438131">"Long message template demo"</string>
-    <string name="long_msg_template_not_supported_text" msgid="3641559637317672505">"Your host doesn\'t support long message template"</string>
+    <string name="list_template_demo_title" msgid="1740208242737246151">"List Template Demo"</string>
+    <string name="long_msg_template_demo_title" msgid="1793748562161438131">"Long Message Template Demo"</string>
+    <string name="long_msg_template_not_supported_text" msgid="3641559637317672505">"Your host doesn\'t support Long Message template"</string>
     <string name="long_msg_template_not_supported_title" msgid="8600719470226274925">"Incompatible host"</string>
-    <string name="msg_template_demo_title" msgid="3895210951340409473">"Message template demo"</string>
+    <string name="msg_template_demo_title" msgid="3895210951340409473">"Message Template Demo"</string>
     <string name="msg_template_demo_text" msgid="2275291617716161409">"Message goes here.\nMore text on second line."</string>
-    <string name="short_msg_template_demo_title" msgid="6798738013668580714">"Short message template demo"</string>
-    <string name="pane_template_demo_title" msgid="7804292600060341608">"Pane template demo"</string>
-    <string name="place_list_template_demo_title" msgid="2054022985455460469">"Place list template demo"</string>
-    <string name="browse_places_title" msgid="7246005909846715898">"Browse places"</string>
-    <string name="search_template_demo_title" msgid="1770474418958318114">"Search template demo"</string>
+    <string name="short_msg_template_demo_title" msgid="6798738013668580714">"Short Message Template Demo"</string>
+    <string name="pane_template_demo_title" msgid="7804292600060341608">"Pane Template Demo"</string>
+    <string name="place_list_template_demo_title" msgid="2054022985455460469">"Place List Template Demo"</string>
+    <string name="browse_places_title" msgid="7246005909846715898">"Browse Places"</string>
+    <string name="search_template_demo_title" msgid="1770474418958318114">"Search Template Demo"</string>
     <string name="search_hint" msgid="978495498991026792">"Search here"</string>
-    <string name="additional_text" msgid="8410289578276941586">"Please review our Terms of Service"</string>
-    <string name="google_sign_in" msgid="6556259799319701727">"Google Sign-In"</string>
+    <string name="additional_text" msgid="8410289578276941586">"Please review our terms of service"</string>
+    <string name="google_sign_in" msgid="6556259799319701727">"Google sign-in"</string>
     <string name="use_pin" msgid="7850893299484337431">"Use PIN"</string>
-    <string name="qr_code" msgid="5487041647280777397">"QR code"</string>
-    <string name="sign_in_template_not_supported_text" msgid="7184733753948837646">"Your host doesn\'t support Sign-In template"</string>
+    <string name="qr_code" msgid="5487041647280777397">"QR Code"</string>
+    <string name="sign_in_template_not_supported_text" msgid="7184733753948837646">"Your host doesn\'t support Sign In template"</string>
     <string name="sign_in_template_not_supported_title" msgid="4892883228898541764">"Incompatible host"</string>
     <string name="email_hint" msgid="7205549445477319606">"Email"</string>
     <string name="sign_in_title" msgid="4551967308262681703">"Sign in"</string>
@@ -251,97 +251,97 @@
     <string name="password_hint" msgid="2869107073860012864">"password"</string>
     <string name="password_sign_in_instruction_prefix" msgid="9105788349198243508">"Username"</string>
     <string name="pin_sign_in_instruction" msgid="2288691296234360441">"Type this PIN in your phone"</string>
-    <string name="qr_code_sign_in_title" msgid="8137070561006464518">"Scan QR code to sign in"</string>
+    <string name="qr_code_sign_in_title" msgid="8137070561006464518">"Scan QR Code to sign in"</string>
     <string name="sign_in_with_google_title" msgid="8043752000786977249">"Sign in with Google"</string>
-    <string name="provider_sign_in_instruction" msgid="7586815688292506743">"Use this button to complete your Google Sign-In"</string>
+    <string name="provider_sign_in_instruction" msgid="7586815688292506743">"Use this button to complete your Google sign-in"</string>
     <string name="sign_in_complete_text" msgid="8423984266325680606">"You are signed in!"</string>
     <string name="sign_in_complete_title" msgid="8919868148773983428">"Sign in completed"</string>
-    <string name="sign_in_template_demo_title" msgid="6052035424941410249">"Sign in template demo"</string>
+    <string name="sign_in_template_demo_title" msgid="6052035424941410249">"Sign In Template Demo"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Images cannot be displayed for an unknown host"</string>
     <string name="icon_title_prefix" msgid="8487026131229541244">"Icon"</string>
-    <string name="content_provider_icons_demo_title" msgid="5708602618664311097">"Content provider icons demo"</string>
-    <string name="icons_demo_title" msgid="4082976685262307357">"Icons demo"</string>
+    <string name="content_provider_icons_demo_title" msgid="5708602618664311097">"Content Provider Icons Demo"</string>
+    <string name="icons_demo_title" msgid="4082976685262307357">"Icons Demo"</string>
     <string name="app_icon_title" msgid="7534936683349723423">"The app icon"</string>
     <string name="vector_no_tint_title" msgid="874591632279039146">"A vector drawable, without a tint"</string>
     <string name="vector_with_tint_title" msgid="1022346419829696827">"A vector drawable, with a tint"</string>
-    <string name="vector_with_app_theme_attr_title" msgid="4890094482708376219">"A vector drawable, with an app\'s theme attribute for its colour"</string>
+    <string name="vector_with_app_theme_attr_title" msgid="4890094482708376219">"A vector drawable, with an app\'s theme attribute for its color"</string>
     <string name="png_res_title" msgid="7437083018336747544">"A PNG, sent as a resource"</string>
     <string name="png_bitmap_title" msgid="3385912074130977032">"A PNG, sent as a bitmap"</string>
     <string name="just_row_title" msgid="965700021568970725">"Just a title"</string>
     <string name="title_with_app_icon_row_title" msgid="6294250714820740520">"Title with app icon"</string>
     <string name="title_with_res_id_image_row_title" msgid="3813134904602875778">"Title with resource ID image"</string>
     <string name="title_with_svg_image_row_title" msgid="6109092343637263755">"Title with SVG image"</string>
-    <string name="colored_secondary_row_title" msgid="5173288934252528929">"Coloured secondary text"</string>
+    <string name="colored_secondary_row_title" msgid="5173288934252528929">"Colored secondary text"</string>
     <string name="title_with_secondary_lines_row_title" msgid="7769408881272549837">"Title with multiple secondary text lines"</string>
     <string name="title_with_secondary_lines_row_text_1" msgid="8945114692653524102">"Err and err and err again, but less and less and less."</string>
     <string name="title_with_secondary_lines_row_text_2" msgid="1017734603405251531">"- Piet Hein"</string>
-    <string name="rows_demo_title" msgid="3198566660454251007">"Rows demo"</string>
-    <string name="text_icons_demo_title" msgid="8732943920672143201">"Text and icons demos"</string>
-    <string name="row_text_icons_demo_title" msgid="135167694047524905">"Rows with text and icons demo"</string>
-    <string name="radio_button_list_demo_title" msgid="9082264324855338774">"Radio button lists demo"</string>
-    <string name="selectable_lists_demo_title" msgid="5492658731113129386">"Selectable lists demo"</string>
+    <string name="rows_demo_title" msgid="3198566660454251007">"Rows Demo"</string>
+    <string name="text_icons_demo_title" msgid="8732943920672143201">"Text and Icons Demos"</string>
+    <string name="row_text_icons_demo_title" msgid="135167694047524905">"Rows with Text and Icons Demo"</string>
+    <string name="radio_button_list_demo_title" msgid="9082264324855338774">"Radio Button Lists Demo"</string>
+    <string name="selectable_lists_demo_title" msgid="5492658731113129386">"Selectable Lists Demo"</string>
     <string name="option_1_title" msgid="7221252541651471199">"Option 1"</string>
     <string name="option_2_title" msgid="1905146448697963818">"Option 2"</string>
     <string name="option_3_title" msgid="6319268250436119258">"Option 3"</string>
-    <string name="option_row_radio_title" msgid="5978617101267398181">"Row with radio button"</string>
-    <string name="option_row_radio_icon_title" msgid="3304229002524317977">"Row with radio button and icon"</string>
-    <string name="option_row_radio_icon_colored_text_title" msgid="947641896184637026">"Row with radio button and icon and coloured text"</string>
+    <string name="option_row_radio_title" msgid="5978617101267398181">"Row with Radio Button"</string>
+    <string name="option_row_radio_icon_title" msgid="3304229002524317977">"Row with Radio Button and Icon"</string>
+    <string name="option_row_radio_icon_colored_text_title" msgid="947641896184637026">"Row with Radio Button and Icon and Colored Text"</string>
     <string name="some_additional_text" msgid="4009872495806318260">"Some additional text"</string>
     <string name="sample_additional_list" msgid="5085372891301576306">"Sample selectable list"</string>
-    <string name="task_restriction_demo_title" msgid="2212084350718766941">"Task restriction demo"</string>
+    <string name="task_restriction_demo_title" msgid="2212084350718766941">"Task Restriction Demo"</string>
     <string name="task_limit_reached_msg" msgid="6038763366777119364">"Task limit reached\nGoing forward will force stop the app"</string>
     <string name="task_step_of_title" msgid="2791717962535723839">"Task step %1$d of %2$d"</string>
     <string name="task_step_of_text" msgid="4646729781462227219">"Click to go forward"</string>
-    <string name="task_content_allowed" msgid="545061986612431190">"Please visit the different templates and ensure that the car is in driving mode"</string>
-    <string name="toggle_button_demo_title" msgid="3179103600967398928">"Toggle button demo"</string>
+    <string name="task_content_allowed" msgid="545061986612431190">"Please visit the different templates and ensure the car is in driving mode"</string>
+    <string name="toggle_button_demo_title" msgid="3179103600967398928">"Toggle Button Demo"</string>
     <string name="toggle_test_title" msgid="924485265152862631">"Toggle test"</string>
     <string name="toggle_test_text" msgid="8107217216013312857">"Stateful changes are allowed"</string>
-    <string name="toggle_test_first_toggle_title" msgid="3635022201072117680">"Enable toggle test"</string>
-    <string name="toggle_test_first_toggle_text" msgid="5914741538328669668">"Tick this one to enable the toggle test"</string>
+    <string name="toggle_test_first_toggle_title" msgid="3635022201072117680">"Enable Toggle Test"</string>
+    <string name="toggle_test_first_toggle_text" msgid="5914741538328669668">"Check this one to enable the toggle test"</string>
     <string name="toggle_test_second_toggle_title" msgid="1083594617400613969">"Toggle test"</string>
     <string name="toggle_test_second_toggle_text" msgid="1813071017415876745">"Stateful changes are allowed"</string>
     <string name="image_test_title" msgid="8273863429801477547">"Image test"</string>
     <string name="image_test_text" msgid="6264812093895530445">"Image changes are allowed"</string>
-    <string name="additional_data_title" msgid="3546689652240300617">"Additional data"</string>
+    <string name="additional_data_title" msgid="3546689652240300617">"Additional Data"</string>
     <string name="additional_data_text" msgid="2846223398214158872">"Updates allowed on back operations."</string>
-    <string name="toggle_test_enabled" msgid="982370904182034076">"Toggle test enabled"</string>
-    <string name="toggle_test_disabled" msgid="8366040658408451664">"Toggle test disabled"</string>
-    <string name="secondary_actions_decoration_button_demo_title" msgid="3710817648501132309">"Secondary action and decoration demo"</string>
-    <string name="secondary_actions_test_title" msgid="3664453747553733613">"Secondary action test"</string>
+    <string name="toggle_test_enabled" msgid="982370904182034076">"Toggle Test Enabled"</string>
+    <string name="toggle_test_disabled" msgid="8366040658408451664">"Toggle Test Disabled"</string>
+    <string name="secondary_actions_decoration_button_demo_title" msgid="3710817648501132309">"Secondary Action and Decoration Demo"</string>
+    <string name="secondary_actions_test_title" msgid="3664453747553733613">"Secondary Action Test"</string>
     <string name="secondary_actions_test_subtitle" msgid="6985282813402073703">"Only the secondary action can be selected"</string>
-    <string name="decoration_test_title" msgid="8450127046762442244">"Decoration test"</string>
-    <string name="secondary_actions_decoration_test_title" msgid="6282873404859209490">"Secondary actions and decoration"</string>
+    <string name="decoration_test_title" msgid="8450127046762442244">"Decoration Test"</string>
+    <string name="secondary_actions_decoration_test_title" msgid="6282873404859209490">"Secondary Actions and Decoration"</string>
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"The row can also be selected"</string>
-    <string name="secondary_action_toast" msgid="5076434693504006565">"Secondary action is selected"</string>
+    <string name="secondary_action_toast" msgid="5076434693504006565">"Secondary Action is selected"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Row primary action is selected"</string>
-    <string name="misc_templates_demos_title" msgid="6077169010255928114">"Misc templates demos"</string>
-    <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase demos"</string>
-    <string name="template_layouts_demo_title" msgid="788249269446087847">"Template layout demos"</string>
-    <string name="grid_template_menu_demo_title" msgid="7096285873490705119">"Grid template demos"</string>
-    <string name="voice_access_demo_title" msgid="3825223890895361496">"Voice Access demo screen"</string>
-    <string name="user_interactions_demo_title" msgid="1356952319161314986">"User interactions"</string>
-    <string name="request_permission_menu_demo_title" msgid="4796486779527427017">"Request permissions demos"</string>
-    <string name="application_overflow_title" msgid="396427940886169325">"Application overflow validator"</string>
+    <string name="misc_templates_demos_title" msgid="6077169010255928114">"Misc Templates Demos"</string>
+    <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase Demos"</string>
+    <string name="template_layouts_demo_title" msgid="788249269446087847">"Template Layout Demos"</string>
+    <string name="grid_template_menu_demo_title" msgid="7096285873490705119">"Grid Template Demos"</string>
+    <string name="voice_access_demo_title" msgid="3825223890895361496">"Voice Access Demo Screen"</string>
+    <string name="user_interactions_demo_title" msgid="1356952319161314986">"User Interactions"</string>
+    <string name="request_permission_menu_demo_title" msgid="4796486779527427017">"Request Permissions Demos"</string>
+    <string name="application_overflow_title" msgid="396427940886169325">"Application Overflow Validator"</string>
     <string name="application_overflow_subtitle1" msgid="7429415605726615529">"Please test the following templates while changing"</string>
     <string name="application_overflow_subtitle2" msgid="4385123036846369714">"the vehicle from parked to driving state"</string>
-    <string name="perm_group" msgid="3834918337351876270">"Permission group"</string>
-    <string name="perm_group_description" msgid="7348847631139139024">"Permission group for Showcase app"</string>
-    <string name="perm_fine_location" msgid="5438874642600304118">"Access to fine location"</string>
-    <string name="perm_fine_location_desc" msgid="3549183883787912516">"Permission for access to fine location"</string>
-    <string name="perm_coarse_location" msgid="6140337431619481015">"Access to coarse location"</string>
-    <string name="perm_coarse_location_desc" msgid="6074759942301565943">"Permission for access to coarse location"</string>
-    <string name="perm_record_audio" msgid="2758340693260523493">"Access to record audio"</string>
-    <string name="perm_record_audio_desc" msgid="8038648467605928912">"Permission for access to record audio"</string>
+    <string name="perm_group" msgid="3834918337351876270">"Permission Group"</string>
+    <string name="perm_group_description" msgid="7348847631139139024">"Permission Group for Showcase App"</string>
+    <string name="perm_fine_location" msgid="5438874642600304118">"Access to Fine Location"</string>
+    <string name="perm_fine_location_desc" msgid="3549183883787912516">"Permission for Access to Fine Location"</string>
+    <string name="perm_coarse_location" msgid="6140337431619481015">"Access to Coarse Location"</string>
+    <string name="perm_coarse_location_desc" msgid="6074759942301565943">"Permission for Access to Coarse Location"</string>
+    <string name="perm_record_audio" msgid="2758340693260523493">"Access to Record Audio"</string>
+    <string name="perm_record_audio_desc" msgid="8038648467605928912">"Permission for Access to Record Audio"</string>
     <string name="location_1_description" msgid="4801052291684791371">"Tinted resource vector"</string>
     <string name="location_2_description" msgid="3331356135359047166">"Image resource bitmap"</string>
-    <string name="location_3_description" msgid="3982142774088944850">"Coloured text marker"</string>
+    <string name="location_3_description" msgid="3982142774088944850">"Colored text marker"</string>
     <string name="location_4_description" msgid="6560365445044381911">"Image bitmap"</string>
     <string name="location_description_text_label" msgid="2779911545316756419">"Text label"</string>
-    <string name="parking_vs_driving_demo_title" msgid="3367862800135053111">"Parking vs driving demo"</string>
-    <string name="latest_feature_details" msgid="6843008350392721502">"Cluster displays in cars!"</string>
-    <string name="latest_feature_title" msgid="7929405790070777460">"Latest features"</string>
+    <string name="parking_vs_driving_demo_title" msgid="3367862800135053111">"Parking Vs Driving Demo"</string>
+    <string name="latest_feature_details" msgid="6843008350392721502">"Cluster Displays in cars!"</string>
+    <string name="latest_feature_title" msgid="7929405790070777460">"Latest Features"</string>
     <string name="loading_toggle_enabled" msgid="8828072732804454994">"Loading enabled"</string>
     <string name="loading_toggle_disabled" msgid="7689738885077382673">"Loading disabled"</string>
     <string name="loading_screen" msgid="4771507490730308794">"Loading screen"</string>
-    <string name="vector_toggle_details" msgid="1301305340033556819">"Toggle to add/remove colour"</string>
+    <string name="vector_toggle_details" msgid="1301305340033556819">"Toggle to add/remove color"</string>
 </resources>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
index f8d344e..8be8cde 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
@@ -292,8 +292,7 @@
     <string name="task_limit_reached_msg" msgid="6038763366777119364">"Alcanzouse o límite de tarefas\nSe continúas, forzarase a detención da aplicación"</string>
     <string name="task_step_of_title" msgid="2791717962535723839">"Paso %1$d de %2$d da tarefa"</string>
     <string name="task_step_of_text" msgid="4646729781462227219">"Fai clic para continuar"</string>
-    <!-- no translation found for task_content_allowed (545061986612431190) -->
-    <skip />
+    <string name="task_content_allowed" msgid="545061986612431190">"Consulta os diferentes modelos e comproba que o coche estea no modo de condución"</string>
     <string name="toggle_button_demo_title" msgid="3179103600967398928">"Demostración de botón de activación/desactivación"</string>
     <string name="toggle_test_title" msgid="924485265152862631">"Proba do interruptor"</string>
     <string name="toggle_test_text" msgid="8107217216013312857">"Os cambios de estado están permitidos"</string>
@@ -322,12 +321,9 @@
     <string name="voice_access_demo_title" msgid="3825223890895361496">"Pantalla de demostración de Voice Access"</string>
     <string name="user_interactions_demo_title" msgid="1356952319161314986">"Interaccións do usuario"</string>
     <string name="request_permission_menu_demo_title" msgid="4796486779527427017">"Demostracións de solicitude de permisos"</string>
-    <!-- no translation found for application_overflow_title (396427940886169325) -->
-    <skip />
-    <!-- no translation found for application_overflow_subtitle1 (7429415605726615529) -->
-    <skip />
-    <!-- no translation found for application_overflow_subtitle2 (4385123036846369714) -->
-    <skip />
+    <string name="application_overflow_title" msgid="396427940886169325">"Validador do menú adicional da aplicación"</string>
+    <string name="application_overflow_subtitle1" msgid="7429415605726615529">"Proba os seguintes modelos ao cambiar"</string>
+    <string name="application_overflow_subtitle2" msgid="4385123036846369714">"o vehículo do estado de estacionamento ao de condución"</string>
     <string name="perm_group" msgid="3834918337351876270">"Grupo de permisos"</string>
     <string name="perm_group_description" msgid="7348847631139139024">"Grupo de permisos para a aplicación Escaparate"</string>
     <string name="perm_fine_location" msgid="5438874642600304118">"Acceso a Localización precisa"</string>
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index 306320a..0497df5 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -482,6 +482,15 @@
 
 }
 
+package androidx.car.app.mediaextensions {
+
+  public final class MetadataExtras {
+    field public static final String KEY_DESCRIPTION_LINK_MEDIA_ID = "androidx.car.app.mediaextensions.KEY_DESCRIPTION_LINK_MEDIA_ID";
+    field public static final String KEY_SUBTITLE_LINK_MEDIA_ID = "androidx.car.app.mediaextensions.KEY_SUBTITLE_LINK_MEDIA_ID";
+  }
+
+}
+
 package androidx.car.app.model {
 
   @androidx.car.app.annotations.CarProtocol public final class Action {
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index 8157779..8705415 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -844,6 +844,15 @@
 
 }
 
+package androidx.car.app.mediaextensions {
+
+  public final class MetadataExtras {
+    field public static final String KEY_DESCRIPTION_LINK_MEDIA_ID = "androidx.car.app.mediaextensions.KEY_DESCRIPTION_LINK_MEDIA_ID";
+    field public static final String KEY_SUBTITLE_LINK_MEDIA_ID = "androidx.car.app.mediaextensions.KEY_SUBTITLE_LINK_MEDIA_ID";
+  }
+
+}
+
 package androidx.car.app.messaging {
 
   @androidx.car.app.annotations.ExperimentalCarApi public class MessagingServiceConstants {
@@ -870,7 +879,18 @@
     method public androidx.car.app.messaging.model.CarMessage.Builder setSender(androidx.core.app.Person);
   }
 
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi public interface ConversationCallback {
+    method public void onMarkAsRead();
+    method public void onTextReply(String);
+  }
+
+  @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(6) public interface ConversationCallbackDelegate {
+    method public void sendMarkAsRead(androidx.car.app.OnDoneCallback);
+    method public void sendTextReply(String, androidx.car.app.OnDoneCallback);
+  }
+
   @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(6) public class ConversationItem implements androidx.car.app.model.Item {
+    method public androidx.car.app.messaging.model.ConversationCallbackDelegate getConversationCallbackDelegate();
     method public androidx.car.app.model.CarIcon? getIcon();
     method public String getId();
     method public java.util.List<androidx.car.app.messaging.model.CarMessage!> getMessages();
@@ -881,6 +901,7 @@
   public static final class ConversationItem.Builder {
     ctor public ConversationItem.Builder();
     method public androidx.car.app.messaging.model.ConversationItem build();
+    method public androidx.car.app.messaging.model.ConversationItem.Builder setConversationCallback(androidx.car.app.messaging.model.ConversationCallback);
     method public androidx.car.app.messaging.model.ConversationItem.Builder setGroupConversation(boolean);
     method public androidx.car.app.messaging.model.ConversationItem.Builder setIcon(androidx.car.app.model.CarIcon);
     method public androidx.car.app.messaging.model.ConversationItem.Builder setId(String);
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index 306320a..0497df5 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -482,6 +482,15 @@
 
 }
 
+package androidx.car.app.mediaextensions {
+
+  public final class MetadataExtras {
+    field public static final String KEY_DESCRIPTION_LINK_MEDIA_ID = "androidx.car.app.mediaextensions.KEY_DESCRIPTION_LINK_MEDIA_ID";
+    field public static final String KEY_SUBTITLE_LINK_MEDIA_ID = "androidx.car.app.mediaextensions.KEY_SUBTITLE_LINK_MEDIA_ID";
+  }
+
+}
+
 package androidx.car.app.model {
 
   @androidx.car.app.annotations.CarProtocol public final class Action {
diff --git a/car/app/app/src/main/aidl/androidx/car/app/messaging/model/IConversationCallback.aidl b/car/app/app/src/main/aidl/androidx/car/app/messaging/model/IConversationCallback.aidl
new file mode 100644
index 0000000..44042e2
--- /dev/null
+++ b/car/app/app/src/main/aidl/androidx/car/app/messaging/model/IConversationCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.car.app.messaging.model;
+
+import androidx.car.app.IOnDoneCallback;
+
+/**
+ * Handles Host -> Client IPC calls for a conversation.
+ *
+ * @hide
+ */
+oneway interface IConversationCallback {
+  /**
+   * Notifies the app that it should mark all messages in the current conversation as read
+   */
+  void onMarkAsRead(IOnDoneCallback callback) = 1;
+  /**
+   * Notifies the app that it should send a reply to a given conversation
+   */
+  void onTextReply(IOnDoneCallback callback, String replyText) = 2;
+}
\ No newline at end of file
diff --git a/car/app/app/src/main/java/androidx/car/app/mediaextensions/MetadataExtras.java b/car/app/app/src/main/java/androidx/car/app/mediaextensions/MetadataExtras.java
new file mode 100644
index 0000000..1ad83f2
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/mediaextensions/MetadataExtras.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.mediaextensions;
+
+import android.os.Bundle;
+
+/**
+ * Defines constants for extra keys in {@link android.support.v4.media.MediaMetadataCompat} or
+ * {@link androidx.media3.common.MediaMetadata}.
+ */
+public final class MetadataExtras {
+
+    // Do not instantiate
+    private MetadataExtras() {
+    }
+
+    /**
+     * {@link Bundle} key used in the extras of a media item to indicate that the subtitle of the
+     * corresponding media item can be linked to another media item ID.
+     * <p>The value of the extra is set to the media ID of this other item.
+     *
+     * <p>NOTE: media1 and media3 apps setting this extra <b>must implement</b>
+     * {@link androidx.media.MediaBrowserServiceCompat#onLoadItem} or
+     * {@link  androidx.media3.session.MediaLibraryService.Callback#onGetItem} respectively.
+     * <p>NOTE: media apps setting this extra <b>must explicitly set the subtitle property</b>.
+     * <p>See {@link android.support.v4.media.MediaMetadataCompat#METADATA_KEY_DISPLAY_SUBTITLE}
+     * <p>See {@link androidx.media3.common.MediaMetadata#subtitle}
+     *
+     * <p>TYPE: String.
+     * <p>
+     * <p> Example:
+     * <pre>
+     *   "Source" MediaItem
+     *      + mediaId:                  “Beethoven-9th-symphony”    // ID
+     *      + title:                    “9th symphony”              // Track
+     *      + subtitle:                 “The best of Beethoven”     // Album
+     * ╔════+ subtitleLinkMediaId:      “Beethoven-best-of”         // Album ID
+     * ║    + description:              “Beethoven”                 // Artist
+     * ║    + descriptionLinkMediaId:   “artist:Beethoven”          // Artist ID
+     * ║
+     * ║ "Destination" MediaItem
+     * ╚════+ mediaId:                  “Beethoven-best-of”         // ID
+     *      + title:                    “The best of Beethoven”     // Album
+     *      + subtitle:                 “Beethoven”                 // Artist
+     *      + subtitleLinkMediaId:      “artist:Beethoven”          // Artist ID
+     * </pre>
+     **/
+    public static final String KEY_SUBTITLE_LINK_MEDIA_ID =
+            "androidx.car.app.mediaextensions.KEY_SUBTITLE_LINK_MEDIA_ID";
+
+    /**
+     * {@link Bundle} key used in the extras of a media item to indicate that the description of the
+     * corresponding media item can be linked to another media item ID.
+     * <p>The value of the extra is set to the media ID of this other item.
+     *
+     * <p>NOTE: media1 and media3 apps setting this extra <b>must implement</b>
+     * {@link androidx.media.MediaBrowserServiceCompat#onLoadItem} or
+     * {@link androidx.media3.session.MediaLibraryService.Callback#onGetItem} respectively.
+     * <p>NOTE: media apps setting this extra <b>must explicitly set the description property</b>.
+     * <p>See {@link android.support.v4.media.MediaMetadataCompat#METADATA_KEY_DISPLAY_DESCRIPTION}
+     * <p>See {@link androidx.media3.common.MediaMetadata#description}
+     *
+     * <p>TYPE: String.
+     * <p>
+     * <p> Example:
+     * <pre>
+     *   "Source" MediaItem
+     *      + mediaId:                  “Beethoven-9th-symphony”    // ID
+     *      + title:                    “9th symphony”              // Track
+     *      + subtitle:                 “The best of Beethoven”     // Album
+     *      + subtitleLinkMediaId:      “Beethoven-best-of”         // Album ID
+     *      + description:              “Beethoven”                 // Artist
+     * ╔════+ descriptionLinkMediaId:   “artist:Beethoven”          // Artist ID
+     * ║
+     * ║ "Destination" MediaItem
+     * ╚════+ mediaId:                  “artist:Beethoven”          // ID
+     *      + title:                    “Beethoven”                 // Artist
+     * </pre>
+     **/
+    public static final String KEY_DESCRIPTION_LINK_MEDIA_ID =
+            "androidx.car.app.mediaextensions.KEY_DESCRIPTION_LINK_MEDIA_ID";
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/mediaextensions/package-info.java b/car/app/app/src/main/java/androidx/car/app/mediaextensions/package-info.java
new file mode 100644
index 0000000..c86a9d5
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/mediaextensions/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The mediaextensions package defines car specific extensions to the
+ * {@link android.support.v4.media}, {@link androidx.media} and {@link androidx.media3} libraries.
+ */
+package androidx.car.app.mediaextensions;
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationCallback.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationCallback.java
new file mode 100644
index 0000000..e369a72
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationCallback.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.messaging.model;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.annotations.CarProtocol;
+import androidx.car.app.annotations.ExperimentalCarApi;
+
+/** Host -> Client callbacks for a {@link ConversationItem} */
+@ExperimentalCarApi
+@CarProtocol
+public interface ConversationCallback {
+    /**
+     * Notifies the app that it should mark all messages in the current conversation as read
+     */
+    void onMarkAsRead();
+
+    /**
+     * Notifies the app that it should send a reply to a given conversation
+     */
+    void onTextReply(@NonNull String replyText);
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationCallbackDelegate.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationCallbackDelegate.java
new file mode 100644
index 0000000..924e566
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationCallbackDelegate.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.messaging.model;
+
+import android.annotation.SuppressLint;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.OnDoneCallback;
+import androidx.car.app.annotations.CarProtocol;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+
+/** Used by the host to invoke {@link ConversationCallback} methods on the client */
+@ExperimentalCarApi
+@CarProtocol
+@RequiresCarApi(6)
+public interface ConversationCallbackDelegate {
+
+    /** Called from the host to invoke {@link ConversationCallback#onMarkAsRead()} on the client. */
+    // This mirrors the AIDL class and is not supported to support an executor as an input.
+    @SuppressLint("ExecutorRegistration")
+    void sendMarkAsRead(@NonNull OnDoneCallback onDoneCallback);
+
+    /**
+     * Called from the host to invoke {@link ConversationCallback#onTextReply(String)} on the
+     * client.
+     */
+    // This mirrors the AIDL class and is not supported to support an executor as an input.
+    @SuppressLint("ExecutorRegistration")
+    void sendTextReply(@NonNull String replyText, @NonNull OnDoneCallback onDoneCallback);
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationCallbackDelegateImpl.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationCallbackDelegateImpl.java
new file mode 100644
index 0000000..a31fd66
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationCallbackDelegateImpl.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.messaging.model;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.os.RemoteException;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.car.app.IOnDoneCallback;
+import androidx.car.app.OnDoneCallback;
+import androidx.car.app.annotations.CarProtocol;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+import androidx.car.app.utils.RemoteUtils;
+
+/**
+ * Handles binder transactions related to {@link ConversationCallback}
+ *
+ * <p> This class exists because we don't want to expose {@link IConversationCallback} to the A4C
+ * client.
+ *
+ * @hide
+ */
+@ExperimentalCarApi
+@RestrictTo(LIBRARY)
+@CarProtocol
+@RequiresCarApi(6)
+class ConversationCallbackDelegateImpl implements ConversationCallbackDelegate {
+    @Keep
+    @Nullable
+    private final IConversationCallback mConversationCallbackBinder;
+
+    ConversationCallbackDelegateImpl(@NonNull ConversationCallback conversationCallback) {
+        mConversationCallbackBinder = new ConversationCallbackStub(conversationCallback);
+    }
+
+    /** Default constructor for serialization. */
+    private ConversationCallbackDelegateImpl() {
+        mConversationCallbackBinder = null;
+    }
+
+    @Override
+    public void sendMarkAsRead(@NonNull OnDoneCallback onDoneCallback) {
+        try {
+            requireNonNull(mConversationCallbackBinder)
+                    .onMarkAsRead(RemoteUtils.createOnDoneCallbackStub(onDoneCallback));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void sendTextReply(@NonNull String replyText, @NonNull OnDoneCallback onDoneCallback) {
+        try {
+            requireNonNull(mConversationCallbackBinder).onTextReply(
+                    RemoteUtils.createOnDoneCallbackStub(onDoneCallback),
+                    replyText
+            );
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static class ConversationCallbackStub extends IConversationCallback.Stub {
+        @Keep
+        @NonNull
+        private final ConversationCallback mConversationCallback;
+
+        ConversationCallbackStub(@NonNull ConversationCallback conversationCallback) {
+            mConversationCallback = conversationCallback;
+        }
+
+        @Override
+        public void onMarkAsRead(@NonNull IOnDoneCallback onDoneCallback) {
+            RemoteUtils.dispatchCallFromHost(
+                    onDoneCallback,
+                    "onMarkAsRead", () -> {
+                        mConversationCallback.onMarkAsRead();
+                        return null;
+                    }
+            );
+        }
+
+        @Override
+        public void onTextReply(
+                @NonNull IOnDoneCallback onDoneCallback,
+                @NonNull String replyText
+        ) {
+            RemoteUtils.dispatchCallFromHost(
+                    onDoneCallback,
+                    "onReply", () -> {
+                        mConversationCallback.onTextReply(replyText);
+                        return null;
+                    }
+            );
+        }
+    }
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
index 42eeeba..2b70b2c 100644
--- a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
@@ -18,6 +18,8 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.SuppressLint;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.car.app.annotations.CarProtocol;
@@ -35,12 +37,17 @@
 @CarProtocol
 @RequiresCarApi(6)
 public class ConversationItem implements Item {
-    @NonNull private final String mId;
-    @NonNull private final CarText mTitle;
+    @NonNull
+    private final String mId;
+    @NonNull
+    private final CarText mTitle;
     @Nullable
     private final CarIcon mIcon;
     private final boolean mIsGroupConversation;
-    @NonNull private final List<CarMessage> mMessages;
+    @NonNull
+    private final List<CarMessage> mMessages;
+    @NonNull
+    private final ConversationCallbackDelegate mConversationCallbackDelegate;
 
     ConversationItem(@NonNull Builder builder) {
         this.mId = requireNonNull(builder.mId);
@@ -48,6 +55,8 @@
         this.mIcon = builder.mIcon;
         this.mIsGroupConversation = builder.mIsGroupConversation;
         this.mMessages = requireNonNull(builder.mMessages);
+        this.mConversationCallbackDelegate = new ConversationCallbackDelegateImpl(
+                requireNonNull(builder.mConversationCallback));
     }
 
     /** Default constructor for serialization. */
@@ -57,6 +66,18 @@
         mIcon = null;
         mIsGroupConversation = false;
         mMessages = new ArrayList<>();
+        mConversationCallbackDelegate = new ConversationCallbackDelegateImpl(
+                new ConversationCallback() {
+                    @Override
+                    public void onMarkAsRead() {
+                        // Do nothing
+                    }
+
+                    @Override
+                    public void onTextReply(@NonNull String replyText) {
+                        // Do nothing
+                    }
+                });
     }
 
     /**
@@ -64,17 +85,20 @@
      *
      * @see Builder#setId
      */
-    public @NonNull String getId() {
+    @NonNull
+    public String getId() {
         return mId;
     }
 
     /** Returns the title of the conversation */
-    public @NonNull CarText getTitle() {
+    @NonNull
+    public CarText getTitle() {
         return mTitle;
     }
 
     /** Returns a {@link CarIcon} for the conversation, or {@code null} if not set */
-    public @Nullable CarIcon getIcon() {
+    @Nullable
+    public CarIcon getIcon() {
         return mIcon;
     }
 
@@ -88,10 +112,17 @@
     }
 
     /** Returns a list of messages for this {@link ConversationItem} */
-    public @NonNull List<CarMessage> getMessages() {
+    @NonNull
+    public List<CarMessage> getMessages() {
         return mMessages;
     }
 
+    /** Returns host->client callbacks for this conversation */
+    @NonNull
+    public ConversationCallbackDelegate getConversationCallbackDelegate() {
+        return mConversationCallbackDelegate;
+    }
+
     /** A builder for {@link ConversationItem} */
     public static final class Builder {
         @Nullable
@@ -103,6 +134,8 @@
         boolean mIsGroupConversation;
         @Nullable
         List<CarMessage> mMessages;
+        @Nullable
+        ConversationCallback mConversationCallback;
 
         /**
          * Specifies a unique identifier for the conversation
@@ -114,19 +147,22 @@
          *     <li> Identifying {@link ConversationItem}s in "mark as read" / "reply" callbacks
          * </ul>
          */
-        public @NonNull Builder setId(@NonNull String id) {
+        @NonNull
+        public Builder setId(@NonNull String id) {
             mId = id;
             return this;
         }
 
         /** Sets the title of the conversation */
-        public @NonNull Builder setTitle(@NonNull CarText title) {
+        @NonNull
+        public Builder setTitle(@NonNull CarText title) {
             mTitle = title;
             return this;
         }
 
         /** Sets a {@link CarIcon} for the conversation */
-        public @NonNull Builder setIcon(@NonNull CarIcon icon) {
+        @NonNull
+        public Builder setIcon(@NonNull CarIcon icon) {
             mIcon = icon;
             return this;
         }
@@ -141,19 +177,31 @@
          * historical example, message readout may include sender names for group conversations, but
          * omit them for 1:1 conversations.
          */
-        public @NonNull Builder setGroupConversation(boolean isGroupConversation) {
+        @NonNull
+        public Builder setGroupConversation(boolean isGroupConversation) {
             mIsGroupConversation = isGroupConversation;
             return this;
         }
 
         /** Specifies a list of messages for the conversation */
-        public @NonNull Builder setMessages(@NonNull List<CarMessage> messages) {
+        @NonNull
+        public Builder setMessages(@NonNull List<CarMessage> messages) {
             mMessages = messages;
             return this;
         }
 
+        /** Sets a {@link ConversationCallback} for the conversation */
+        @SuppressLint({"MissingGetterMatchingBuilder", "ExecutorRegistration"})
+        @NonNull
+        public Builder setConversationCallback(
+                @NonNull ConversationCallback conversationCallback) {
+            mConversationCallback = conversationCallback;
+            return this;
+        }
+
         /** Returns a new {@link ConversationItem} instance defined by this builder */
-        public @NonNull ConversationItem build() {
+        @NonNull
+        public ConversationItem build() {
             return new ConversationItem(this);
         }
     }
diff --git a/car/app/app/src/main/java/androidx/car/app/suggestion/model/Suggestion.java b/car/app/app/src/main/java/androidx/car/app/suggestion/model/Suggestion.java
index cfa432d..3791896 100644
--- a/car/app/app/src/main/java/androidx/car/app/suggestion/model/Suggestion.java
+++ b/car/app/app/src/main/java/androidx/car/app/suggestion/model/Suggestion.java
@@ -88,7 +88,7 @@
     }
 
     /**
-     * Returns an image to display with the suggestion or {@code null} if not set.
+     * Returns a {@code CarIcon} to display with the suggestion or {@code null} if not set.
      *
      * @see Builder#setIcon(CarIcon)
      */
@@ -226,7 +226,7 @@
         }
 
         /**
-         * Sets su suggestion image to display.
+         * Sets a suggestion image to display.
          *
          * <h4>Image Sizing Guidance</h4>
          *
@@ -235,6 +235,8 @@
          * either one of the dimensions, it will be scaled down to be centered inside the
          * bounding box while preserving the aspect ratio.
          *
+         * Icon images are expected to be tintable.
+         *
          * <p>See {@link CarIcon} for more details related to providing icon and image resources
          * that work with different car screen pixel densities.
          *
diff --git a/car/app/app/src/test/java/androidx/car/app/model/constraints/RowListConstraintsTest.java b/car/app/app/src/test/java/androidx/car/app/model/constraints/RowListConstraintsTest.java
index fa94459..8bb5fca 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/constraints/RowListConstraintsTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/constraints/RowListConstraintsTest.java
@@ -18,7 +18,9 @@
 
 import static org.junit.Assert.assertThrows;
 
+import androidx.annotation.NonNull;
 import androidx.car.app.TestUtils;
+import androidx.car.app.messaging.model.ConversationCallback;
 import androidx.car.app.messaging.model.ConversationItem;
 import androidx.car.app.model.CarText;
 import androidx.car.app.model.ItemList;
@@ -97,6 +99,17 @@
                         .setId("id")
                         .setTitle(CarText.create("title"))
                         .setMessages(new ArrayList<>())
+                        .setConversationCallback(new ConversationCallback() {
+                            @Override
+                            public void onMarkAsRead() {
+                                // do nothing
+                            }
+
+                            @Override
+                            public void onTextReply(@NonNull String replyText) {
+                                // do nothing
+                            }
+                        })
                         .build()
                 )
                 .build();
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index db43f57..6843730 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -262,7 +262,12 @@
                     }
                 }
             }
-        """.trimIndent()
+
+            inline fun <T> Identity(block: () -> T): T = block()
+
+            @Composable
+            fun Stack(content: @Composable () -> Unit) = content()
+        """
     )
 
     @Test
@@ -287,6 +292,7 @@
             fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
               %composer = %composer.startRestartGroup(<>)
               sourceInformation(%composer, "C(Test)<A()>,<M3>,<A()>:Test.kt")
+              val tmp0_marker = %composer.currentMarker
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
@@ -302,6 +308,7 @@
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     A(%composer, 0)
                     if (condition) {
+                      %composer.endToMarker(tmp0_marker)
                       if (isTraceInProgress()) {
                         traceEventEnd()
                       }
@@ -359,6 +366,7 @@
             fun Test(a: Boolean, b: Boolean, %composer: Composer?, %changed: Int) {
               %composer = %composer.startRestartGroup(<>)
               sourceInformation(%composer, "C(Test)<A()>,<M3>,<M3>,<A()>:Test.kt")
+              val tmp0_marker = %composer.currentMarker
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
@@ -377,6 +385,7 @@
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     A(%composer, 0)
                     if (a) {
+                      %composer.endToMarker(tmp0_marker)
                       if (isTraceInProgress()) {
                         traceEventEnd()
                       }
@@ -397,6 +406,7 @@
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     A(%composer, 0)
                     if (b) {
+                      %composer.endToMarker(tmp0_marker)
                       if (isTraceInProgress()) {
                         traceEventEnd()
                       }
@@ -456,12 +466,14 @@
                   traceEventStart(<>, %changed, -1, <>)
                 }
                 A(%composer, 0)
+                val tmp0_marker = %composer.currentMarker
                 M3({ %composer: Composer?, %changed: Int ->
                   %composer.startReplaceableGroup(<>)
                   sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     A(%composer, 0)
                     if (condition) {
+                      %composer.endToMarker(tmp0_marker)
                     }
                     A(%composer, 0)
                   } else {
@@ -501,6 +513,7 @@
               T {
                 %this%T.compose(composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
                   sourceInformation(%composer, "C<M1>:Test.kt")
+                  val tmp0_marker = %composer.currentMarker
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     if (isTraceInProgress()) {
                       traceEventStart(<>, %changed, -1, <>)
@@ -510,6 +523,7 @@
                       sourceInformation(%composer, "C:Test.kt")
                       if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                         if (condition) {
+                          %composer.endToMarker(tmp0_marker)
                           if (isTraceInProgress()) {
                             traceEventEnd()
                           }
@@ -584,11 +598,13 @@
                   sourceInformation(%composer, "C<A()>,<M1>,<A()>:Test.kt")
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     A(%composer, 0)
+                    val tmp0_marker = %composer.currentMarker
                     M1({ %composer: Composer?, %changed: Int ->
                       %composer.startReplaceableGroup(<>)
                       sourceInformation(%composer, "C:Test.kt")
                       if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                         if (condition) {
+                          %composer.endToMarker(tmp0_marker)
                         }
                       } else {
                         %composer.skipToGroupEnd()
@@ -647,6 +663,7 @@
                   traceEventStart(<>, %changed, -1, <>)
                 }
                 A(%composer, 0)
+                val tmp0_marker = %composer.currentMarker
                 M3({ %composer: Composer?, %changed: Int ->
                   %composer.startReplaceableGroup(<>)
                   sourceInformation(%composer, "C<A()>,<M1>,<A()>:Test.kt")
@@ -657,6 +674,7 @@
                       sourceInformation(%composer, "C:Test.kt")
                       if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                         if (condition) {
+                          %composer.endToMarker(tmp0_marker)
                         }
                       } else {
                         %composer.skipToGroupEnd()
@@ -709,6 +727,7 @@
             fun testInline_M1_W_Return_Func(condition: Boolean, %composer: Composer?, %changed: Int) {
               %composer = %composer.startRestartGroup(<>)
               sourceInformation(%composer, "C(testInline_M1_W_Return_Func)<A()>,<M1>,<A()>:Test.kt")
+              val tmp0_marker = %composer.currentMarker
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
@@ -728,8 +747,7 @@
                     while (true) {
                       A(%composer, 0)
                       if (condition) {
-                        %composer.endReplaceableGroup()
-                        %composer.endReplaceableGroup()
+                        %composer.endToMarker(tmp0_marker)
                         if (isTraceInProgress()) {
                           traceEventEnd()
                         }
@@ -799,12 +817,14 @@
                   traceEventStart(<>, %changed, -1, <>)
                 }
                 A(%composer, 0)
+                val tmp0_marker = %composer.currentMarker
                 M3({ %composer: Composer?, %changed: Int ->
                   %composer.startReplaceableGroup(<>)
                   sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     A(%composer, 0)
                     if (condition) {
+                      %composer.endToMarker(tmp0_marker)
                     }
                     A(%composer, 0)
                   } else {
@@ -812,12 +832,14 @@
                   }
                   %composer.endReplaceableGroup()
                 }, %composer, 0)
+                val tmp1_marker = %composer.currentMarker
                 M3({ %composer: Composer?, %changed: Int ->
                   %composer.startReplaceableGroup(<>)
                   sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     A(%composer, 0)
                     if (condition) {
+                      %composer.endToMarker(tmp1_marker)
                     }
                     A(%composer, 0)
                   } else {
@@ -865,6 +887,7 @@
             fun test_CM1_CCM1_RetFun(condition: Boolean, %composer: Composer?, %changed: Int) {
               %composer = %composer.startRestartGroup(<>)
               sourceInformation(%composer, "C(test_CM1_CCM1_RetFun)<Text("...>,<M1>,<Text("...>:Test.kt")
+              val tmp0_marker = %composer.currentMarker
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
@@ -888,8 +911,7 @@
                         sourceInformation(%composer, "C<Text("...>:Test.kt")
                         if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                           Text("In CCM1", %composer, 0b0110)
-                          %composer.endReplaceableGroup()
-                          %composer.endReplaceableGroup()
+                          %composer.endToMarker(tmp0_marker)
                           if (isTraceInProgress()) {
                             traceEventEnd()
                           }
@@ -951,11 +973,13 @@
               if (isTraceInProgress()) {
                 traceEventStart(<>, %changed, -1, <>)
               }
+              val tmp0_marker = %composer.currentMarker
               FakeBox({ %composer: Composer?, %changed: Int ->
                 %composer.startReplaceableGroup(<>)
                 sourceInformation(%composer, "C<A()>:Test.kt")
                 if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                   if (condition) {
+                    %composer.endToMarker(tmp0_marker)
                   }
                   A(%composer, 0)
                 } else {
@@ -1127,11 +1151,13 @@
                 if (isTraceInProgress()) {
                   traceEventStart(<>, %changed, -1, <>)
                 }
+                val tmp0_marker = %composer.currentMarker
                 IW({ %composer: Composer?, %changed: Int ->
                   %composer.startReplaceableGroup(<>)
                   sourceInformation(%composer, "C<A()>:Test.kt")
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     if (condition) {
+                      %composer.endToMarker(tmp0_marker)
                     }
                     A(%composer, 0)
                   } else {
@@ -1153,6 +1179,178 @@
     )
 
     @Test
+    fun testVerifyEarlyExitFromNonComposable() = verifyInlineReturn(
+        source = """
+            @Composable
+            fun Test(condition: Boolean) {
+                Text("Some text")
+                Identity {
+                    if (condition) return@Test
+                }
+                Text("Some more text")
+            }
+        """,
+        expectedTransformed = """
+            @Composable
+            fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Text("...>,<Text("...>:Test.kt")
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                Text("Some text", %composer, 0b0110)
+                Identity {
+                  if (condition) {
+                    if (isTraceInProgress()) {
+                      traceEventEnd()
+                    }
+                    %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                      Test(condition, %composer, updateChangedFlags(%changed or 0b0001))
+                    }
+                    return
+                  }
+                }
+                Text("Some more text", %composer, 0b0110)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
+    fun testVerifyEarlyExitFromNonComposable_M1() = verifyInlineReturn(
+        source = """
+            @Composable
+            fun Test(condition: Boolean) {
+                Text("Some text")
+                M1 {
+                    Identity {
+                        if (condition) return@Test
+                    }
+                }
+                Text("Some more text")
+            }
+        """,
+        expectedTransformed = """
+            @Composable
+            fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Text("...>,<M1>,<Text("...>:Test.kt")
+              val tmp0_marker = %composer.currentMarker
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                Text("Some text", %composer, 0b0110)
+                M1({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    Identity {
+                      if (condition) {
+                        %composer.endToMarker(tmp0_marker)
+                        if (isTraceInProgress()) {
+                          traceEventEnd()
+                        }
+                        %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                          Test(condition, %composer, updateChangedFlags(%changed or 0b0001))
+                        }
+                        return
+                      }
+                    }
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                Text("Some more text", %composer, 0b0110)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
+    fun testVerifyEarlyExitFromNonComposable_M1_RM1() = verifyInlineReturn(
+        source = """
+            @Composable
+            fun Test(condition: Boolean) {
+                Text("Some text")
+                M1 {
+                    Identity {
+                        if (condition) return@M1
+                    }
+                }
+                Text("Some more text")
+            }
+        """,
+        expectedTransformed = """
+            @Composable
+            fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Text("...>,<M1>,<Text("...>:Test.kt")
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                Text("Some text", %composer, 0b0110)
+                val tmp0_marker = %composer.currentMarker
+                M1({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    Identity {
+                      if (condition) {
+                        %composer.endToMarker(tmp0_marker)
+                      }
+                    }
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                Text("Some more text", %composer, 0b0110)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
     fun testEnsureRuntimeTestWillCompile_CL() = ensureSetup {
         classLoader(
             """
@@ -1203,6 +1401,7 @@
             fun test_CM1_RetFun(condition: Boolean, %composer: Composer?, %changed: Int) {
               %composer = %composer.startRestartGroup(<>)
               sourceInformation(%composer, "C(test_CM1_RetFun)<Text("...>,<M1>,<Text("...>:Test.kt")
+              val tmp0_marker = %composer.currentMarker
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
@@ -1218,6 +1417,7 @@
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     Text("M1 - before", %composer, 0b0110)
                     if (condition) {
+                      %composer.endToMarker(tmp0_marker)
                       if (isTraceInProgress()) {
                         traceEventEnd()
                       }
@@ -5828,4 +6028,4 @@
             }
         """
     )
-}
+}
\ No newline at end of file
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
index 1fe4e9a..1f5756b 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
@@ -155,11 +155,13 @@
                   traceEventStart(<>, %changed, -1, <>)
                 }
                 IW({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     A(%composer, 0)
                   } else {
                     %composer.skipToGroupEnd()
                   }
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 if (isTraceInProgress()) {
                   traceEventEnd()
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
index 656fb00..cbaf15d 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
@@ -3882,6 +3882,77 @@
             }
         """
     )
+
+    @Test // regression test for 204897513
+    fun test_InlineForLoop() = verifyComposeIrTransform(
+        source = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            fun Test() {
+                Bug(listOf(1, 2, 3)) {
+                    Text(it.toString())
+                }
+            }
+
+            @Composable
+            inline fun <T> Bug(items: List<T>, content: @Composable (item: T) -> Unit) {
+                for (item in items) content(item)
+            }
+        """,
+        expectedTransformed = """
+            @Composable
+            fun Test(%composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<Bug(li...>:Test.kt")
+              if (%changed !== 0 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                Bug(listOf(1, 2, 3), { it: Int, %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<Text(i...>:Test.kt")
+                  val %dirty = %changed
+                  if (%changed and 0b1110 === 0) {
+                    %dirty = %dirty or if (%composer.changed(it)) 0b0100 else 0b0010
+                  }
+                  if (%dirty and 0b01011011 !== 0b00010010 || !%composer.skipping) {
+                    Text(it.toString(), %composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(%composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+            @Composable
+            @ComposableInferredTarget(scheme = "[0[0]]")
+            fun <T> Bug(items: List<T>, content: Function3<@[ParameterName(name = 'item')] T, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Bug)P(1)*<conten...>:Test.kt")
+              val tmp0_iterator = items.iterator()
+              while (tmp0_iterator.hasNext()) {
+                val item = tmp0_iterator.next()
+                content(item, %composer, 0b01110000 and %changed)
+              }
+              %composer.endReplaceableGroup()
+            }
+        """,
+        extra = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            fun Text(value: String) {}
+        """
+    )
 }
 
 class FunctionBodySkippingTransformTestsNoSource : FunctionBodySkippingTransformTestsBase() {
@@ -3945,4 +4016,72 @@
             }
         """
     )
+
+    @Test // regression test for 204897513
+    fun test_InlineForLoop_no_source_info() = verifyComposeIrTransform(
+        source = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            private fun Test() {
+                Bug(listOf(1, 2, 3)) {
+                    Text(it.toString())
+                }
+            }
+
+            @Composable
+            private inline fun <T> Bug(items: List<T>, content: @Composable (item: T) -> Unit) {
+                for (item in items) content(item)
+            }
+        """,
+        expectedTransformed = """
+            @Composable
+            private fun Test(%composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              if (%changed !== 0 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                Bug(listOf(1, 2, 3), { it: Int, %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  val %dirty = %changed
+                  if (%changed and 0b1110 === 0) {
+                    %dirty = %dirty or if (%composer.changed(it)) 0b0100 else 0b0010
+                  }
+                  if (%dirty and 0b01011011 !== 0b00010010 || !%composer.skipping) {
+                    Text(it.toString(), %composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(%composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+            @Composable
+            @ComposableInferredTarget(scheme = "[0[0]]")
+            private fun <T> Bug(items: List<T>, content: Function3<@[ParameterName(name = 'item')] T, Composer, Int, Unit>, %composer: Composer?, %changed: Int) {
+              %composer.startReplaceableGroup(<>)
+              val tmp0_iterator = items.iterator()
+              while (tmp0_iterator.hasNext()) {
+                val item = tmp0_iterator.next()
+                content(item, %composer, 0b01110000 and %changed)
+              }
+              %composer.endReplaceableGroup()
+            }
+        """,
+        extra = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            fun Text(value: String) {}
+        """
+    )
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
index 26f1440..c421acc 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
@@ -1626,4 +1626,69 @@
             }
         """
     )
+
+    @Test
+    fun testForEarlyExit() = verifyComposeIrTransform(
+        source = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            fun Test(condition: Boolean) {
+                val value = remember { mutableStateOf(false) }
+                if (!value.value && !condition) return
+                val value2 = remember { mutableStateOf(false) }
+                Text("Text ${'$'}{value.value}, ${'$'}{value2.value}")
+            }
+        """,
+        expectedTransformed = """
+            @Composable
+            fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<rememb...>,<Text("...>:Test.kt")
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                val value = %composer.cache(false) {
+                  mutableStateOf(
+                    value = false
+                  )
+                }
+                if (!value.value && !condition) {
+                  if (isTraceInProgress()) {
+                    traceEventEnd()
+                  }
+                  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                    Test(condition, %composer, updateChangedFlags(%changed or 0b0001))
+                  }
+                  return
+                }
+                val value2 = remember({
+                  mutableStateOf(
+                    value = false
+                  )
+                }, %composer, 0)
+                Text("Text %{value.value}, %{value2.value}", %composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """,
+        extra = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            fun Text(value: String) { }
+        """
+    )
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
index e8ffd3f..d085612 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
@@ -118,6 +118,7 @@
             fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
               %composer = %composer.startRestartGroup(<>)
               sourceInformation(%composer, "C(Test)<A()>,<Wrappe...>,<A()>:Test.kt")
+              val tmp0_marker = %composer.currentMarker
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
@@ -133,6 +134,7 @@
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     A(%composer, 0)
                     if (!condition) {
+                      %composer.endToMarker(tmp0_marker)
                       if (isTraceInProgress()) {
                         traceEventEnd()
                       }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index 79b1f93c..2b9dccf 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -616,9 +616,8 @@
             }
     }
 
-    private val rollbackGroupMarkerEnabled get() = false
-        // Temporarily disabled for b/255722247
-        // currentMarkerProperty != null && endToMarkerFunction != null
+    private val rollbackGroupMarkerEnabled get() =
+        currentMarkerProperty != null && endToMarkerFunction != null
 
     private val endRestartGroupFunction by guardedLazy {
         composerIrClass
@@ -1092,7 +1091,7 @@
                 body.startOffset,
                 body.endOffset,
                 listOfNotNull(
-                    if (collectSourceInformation && scope.isInlinedLambda)
+                    if (scope.isInlinedLambda)
                         irStartReplaceableGroup(body, scope, irFunctionSourceKey())
                     else null,
                     *sourceInformationPreamble.statements.toTypedArray(),
@@ -1100,9 +1099,7 @@
                     *skipPreamble.statements.toTypedArray(),
                     *bodyPreamble.statements.toTypedArray(),
                     transformedBody,
-                    if (collectSourceInformation && scope.isInlinedLambda)
-                        irEndReplaceableGroup()
-                    else null,
+                    if (scope.isInlinedLambda) irEndReplaceableGroup() else null,
                     returnVar?.let { irReturn(declaration.symbol, irGet(it)) }
                 )
             )
@@ -2630,10 +2627,11 @@
                                 }
                             }
                         }
-
+                        scope.updateIntrinsiceRememberSafety(false)
                         break@loop
                     }
-                    if (scope.isInlinedLambda) leavingInlinedLambda = true
+                    if (scope.isInlinedLambda && scope.inComposableCall)
+                        leavingInlinedLambda = true
                 }
                 is Scope.BlockScope -> {
                     blockScopeMarks.add(scope)
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 0facc1d..9548e0e 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -139,6 +139,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollTo(int value, kotlin.coroutines.Continuation<? super java.lang.Float>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
     property public boolean isScrollInProgress;
     property public final int maxValue;
@@ -245,8 +247,12 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface ScrollableState {
     method public float dispatchRawDelta(float delta);
+    method public default boolean getCanScrollBackward();
+    method public default boolean getCanScrollForward();
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(optional androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public default boolean canScrollBackward;
+    property public default boolean canScrollForward;
     property public abstract boolean isScrollInProgress;
   }
 
@@ -514,6 +520,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
@@ -557,7 +565,6 @@
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class GridItemSpan {
     method public int getCurrentLineSpan();
-    property public final int currentLineSpan;
   }
 
   public final class LazyGridDslKt {
@@ -658,6 +665,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 5320f47..05e0a32 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -190,6 +190,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollTo(int value, kotlin.coroutines.Continuation<? super java.lang.Float>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
     property public boolean isScrollInProgress;
     property public final int maxValue;
@@ -298,8 +300,12 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface ScrollableState {
     method public float dispatchRawDelta(float delta);
+    method public default boolean getCanScrollBackward();
+    method public default boolean getCanScrollForward();
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(optional androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public default boolean canScrollBackward;
+    property public default boolean canScrollForward;
     property public abstract boolean isScrollInProgress;
   }
 
@@ -583,6 +589,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
@@ -626,7 +634,7 @@
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class GridItemSpan {
     method public int getCurrentLineSpan();
-    property public final int currentLineSpan;
+    property @androidx.compose.foundation.ExperimentalFoundationApi public final int currentLineSpan;
   }
 
   public final class LazyGridDslKt {
@@ -728,6 +736,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
@@ -944,6 +954,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 0facc1d..9548e0e 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -139,6 +139,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollTo(int value, kotlin.coroutines.Continuation<? super java.lang.Float>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
     property public boolean isScrollInProgress;
     property public final int maxValue;
@@ -245,8 +247,12 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface ScrollableState {
     method public float dispatchRawDelta(float delta);
+    method public default boolean getCanScrollBackward();
+    method public default boolean getCanScrollForward();
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(optional androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public default boolean canScrollBackward;
+    property public default boolean canScrollForward;
     property public abstract boolean isScrollInProgress;
   }
 
@@ -514,6 +520,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
@@ -557,7 +565,6 @@
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class GridItemSpan {
     method public int getCurrentLineSpan();
-    property public final int currentLineSpan;
   }
 
   public final class LazyGridDslKt {
@@ -658,6 +665,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 78d781e..dd28bf30 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -64,6 +64,7 @@
         androidTestImplementation(libs.testRules)
         androidTestImplementation(libs.testRunner)
         androidTestImplementation "androidx.activity:activity-compose:1.3.1"
+        androidTestImplementation("androidx.core:core:1.9.0")
         androidTestImplementation(libs.espressoCore)
         androidTestImplementation(libs.junit)
         androidTestImplementation(libs.kotlinTest)
@@ -133,6 +134,7 @@
                 implementation(project(":test:screenshot:screenshot"))
                 implementation(project(":internal-testutils-runtime"))
                 implementation("androidx.activity:activity-compose:1.3.1")
+                implementation("androidx.core:core:1.9.0")
 
                 implementation(libs.testUiautomator)
                 implementation(libs.testRules)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
index 024b8f1..14a977b 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.demos.snapping.SnappingDemos
 import androidx.compose.foundation.samples.BringIntoViewResponderSample
 import androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
+import androidx.compose.foundation.samples.CanScrollSample
 import androidx.compose.foundation.samples.ControlledScrollableRowSample
 import androidx.compose.foundation.samples.CustomTouchSlopSample
 import androidx.compose.foundation.samples.InteractionSourceFlowSample
@@ -52,6 +53,7 @@
     "Foundation",
     listOf(
         ComposableDemo("Draggable, Scrollable, Zoomable, Focusable") { HighLevelGesturesDemo() },
+        ComposableDemo("Can scroll forward / backward") { CanScrollSample() },
         ComposableDemo("Vertical scroll") { VerticalScrollExample() },
         ComposableDemo("Controlled Scrollable Row") { ControlledScrollableRowSample() },
         ComposableDemo("Draw Modifiers") { DrawModifiersDemo() },
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeInputField.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeInputField.kt
index e51f4fe..6186f91 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeInputField.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeInputField.kt
@@ -40,7 +40,6 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.Dialog
 
 @Preview
 @Composable
@@ -77,13 +76,6 @@
     }
 }
 
-@Composable
-fun DialogInputFieldDemo(onNavigateUp: () -> Unit) {
-    Dialog(onDismissRequest = onNavigateUp) {
-        InputFieldDemo()
-    }
-}
-
 @OptIn(ExperimentalComposeUiApi::class)
 @Composable
 internal fun EditLine(
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/EmojiCompatDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/EmojiCompatDemo.kt
new file mode 100644
index 0000000..b6995d6
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/EmojiCompatDemo.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.demos.text
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun EmojiCompatDemo() {
+    val emoji14MeltingFace = "\uD83E\uDEE0"
+    val emoji13WomanFeedingBaby = "\uD83D\uDC69\uD83C\uDFFE\u200D\uD83C\uDF7C"
+    val emoji13DisguisedFace = "\uD83E\uDD78"
+    val emoji12HoldingHands =
+        "\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D\uD83D\uDC68\uD83C\uDFFF"
+    val emoji12Flamingo = "\uD83E\uDDA9"
+    val emoji11PartyingFace = "\uD83E\uDD73"
+
+    val text = "11: $emoji11PartyingFace 12: $emoji12Flamingo $emoji12HoldingHands " +
+        "13: $emoji13DisguisedFace $emoji13WomanFeedingBaby " +
+        "14: $emoji14MeltingFace"
+
+    Column(modifier = Modifier
+        .fillMaxSize()
+        .padding(16.dp)) {
+        Text(text = text, modifier = Modifier.padding(16.dp))
+
+        val textFieldValue = remember { mutableStateOf(TextFieldValue(text)) }
+
+        TextField(
+            value = textFieldValue.value,
+            modifier = Modifier.padding(16.dp),
+            onValueChange = { textFieldValue.value = it }
+        )
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/FocusTextFieldImmediatelyDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/FocusTextFieldImmediatelyDemo.kt
new file mode 100644
index 0000000..c750f11
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/FocusTextFieldImmediatelyDemo.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.demos.text
+
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+
+@Composable
+fun FocusTextFieldImmediatelyDemo() {
+    val focusRequester = remember { FocusRequester() }
+    var value by remember { mutableStateOf("") }
+
+    DisposableEffect(focusRequester) {
+        focusRequester.requestFocus()
+        onDispose {}
+    }
+
+    TextField(
+        value,
+        onValueChange = { value = it },
+        modifier = Modifier
+            .wrapContentSize()
+            .focusRequester(focusRequester)
+    )
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
index 2b5a89f..6562a8e 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
@@ -73,6 +73,7 @@
                 ComposableDemo("Variable Fonts") { VariableFontsDemo() },
                 ComposableDemo("FontFamily fallback") { FontFamilyDemo() },
                 ComposableDemo("All system font families") { SystemFontFamilyDemo() },
+                ComposableDemo("Emoji Compat") { EmojiCompatDemo() },
             )
         ),
         DemoCategory(
@@ -95,9 +96,7 @@
                 ComposableDemo("Full-screen field") { FullScreenTextFieldDemo() },
                 ComposableDemo("Ime Action") { ImeActionDemo() },
                 ComposableDemo("Ime SingleLine") { ImeSingleLineDemo() },
-                ComposableDemo("Inside Dialog") { onNavigateUp ->
-                    DialogInputFieldDemo(onNavigateUp)
-                },
+                ComposableDemo("Inside Dialog") { TextFieldsInDialogDemo() },
                 ComposableDemo("Inside scrollable") { TextFieldsInScrollableDemo() },
                 ComposableDemo("Keyboard Types") { KeyboardTypeDemo() },
                 ComposableDemo("Min/Max Lines") { BasicTextFieldMinMaxDemo() },
@@ -106,6 +105,7 @@
                 ComposableDemo("Visual Transformation") { VisualTransformationDemo() },
                 ComposableDemo("TextFieldValue") { TextFieldValueDemo() },
                 ComposableDemo("Tail Following Text Field") { TailFollowingTextFieldDemo() },
+                ComposableDemo("Focus immediately") { FocusTextFieldImmediatelyDemo() },
             )
         ),
         DemoCategory(
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextFieldsInDialogDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextFieldsInDialogDemo.kt
new file mode 100644
index 0000000..bce2a5f
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextFieldsInDialogDemo.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalComposeUiApi::class)
+
+package androidx.compose.foundation.demos.text
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.integration.demos.common.ComposableDemo
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.ListItem
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+
+private val dialogDemos = listOf(
+    ComposableDemo("Full screen dialog, multiple fields") { onNavigateUp ->
+        Dialog(onDismissRequest = onNavigateUp) {
+            InputFieldDemo()
+        }
+    },
+    ComposableDemo(
+        "Small dialog, single field (platform default width, decor fits system windows)"
+    ) { onNavigateUp ->
+        Dialog(
+            onDismissRequest = onNavigateUp,
+            properties = DialogProperties(
+                usePlatformDefaultWidth = true,
+                decorFitsSystemWindows = true
+            )
+        ) { SingleTextFieldDialog() }
+    },
+    ComposableDemo(
+        "Small dialog, single field (decor fits system windows)"
+    ) { onNavigateUp ->
+        Dialog(
+            onDismissRequest = onNavigateUp,
+            properties = DialogProperties(
+                usePlatformDefaultWidth = false,
+                decorFitsSystemWindows = true
+            )
+        ) { SingleTextFieldDialog() }
+    },
+    ComposableDemo(
+        "Small dialog, single field (platform default width)"
+    ) { onNavigateUp ->
+        Dialog(
+            onDismissRequest = onNavigateUp,
+            properties = DialogProperties(
+                usePlatformDefaultWidth = true,
+                decorFitsSystemWindows = false
+            )
+        ) { SingleTextFieldDialog() }
+    },
+    ComposableDemo(
+        "Small dialog, single field"
+    ) { onNavigateUp ->
+        Dialog(
+            onDismissRequest = onNavigateUp,
+            properties = DialogProperties(
+                usePlatformDefaultWidth = false,
+                decorFitsSystemWindows = false
+            )
+        ) { SingleTextFieldDialog() }
+    },
+    ComposableDemo("Show keyboard automatically") { onNavigateUp ->
+        Dialog(onDismissRequest = onNavigateUp) {
+            AutoFocusTextFieldDialog()
+        }
+    }
+)
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun TextFieldsInDialogDemo() {
+    val listState = rememberLazyListState()
+    val (currentDemoIndex, setDemoIndex) = rememberSaveable { mutableStateOf(-1) }
+
+    if (currentDemoIndex == -1) {
+        LazyColumn(state = listState) {
+            itemsIndexed(dialogDemos) { index, demo ->
+                ListItem(Modifier.clickable { setDemoIndex(index) }) {
+                    Text(demo.title)
+                }
+            }
+        }
+    } else {
+        val currentDemo = dialogDemos[currentDemoIndex]
+        Text(
+            currentDemo.title,
+            modifier = Modifier
+                .fillMaxSize()
+                .wrapContentSize(),
+            textAlign = TextAlign.Center
+        )
+        Layout(
+            content = { currentDemo.content(onNavigateUp = { setDemoIndex(-1) }) }
+        ) { measurables, _ ->
+            check(measurables.isEmpty()) { "Dialog demo must only emit a Dialog composable." }
+            layout(0, 0) {}
+        }
+    }
+}
+
+@Composable
+private fun SingleTextFieldDialog() {
+    var text by remember { mutableStateOf("") }
+    TextField(text, onValueChange = { text = it })
+}
+
+@Composable
+private fun AutoFocusTextFieldDialog() {
+    var text by remember { mutableStateOf("") }
+    val focusRequester = remember { FocusRequester() }
+
+    LaunchedEffect(focusRequester) {
+        focusRequester.requestFocus()
+    }
+
+    TextField(
+        text,
+        onValueChange = { text = it },
+        modifier = Modifier.focusRequester(focusRequester)
+    )
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ScrollableSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ScrollableSamples.kt
index 94940dd..1645cb76 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ScrollableSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ScrollableSamples.kt
@@ -22,14 +22,24 @@
 import androidx.compose.foundation.gestures.rememberScrollableState
 import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material.Icon
 import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material.icons.filled.KeyboardArrowUp
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
@@ -62,4 +72,44 @@
         // the delta values in the scrollable state
         Text(offset.value.roundToInt().toString(), style = TextStyle(fontSize = 32.sp))
     }
-}
\ No newline at end of file
+}
+
+@Sampled
+@Composable
+fun CanScrollSample() {
+    val state = rememberLazyListState()
+    Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
+        Icon(
+            Icons.Filled.KeyboardArrowUp,
+            null,
+            Modifier.graphicsLayer {
+                // Hide the icon if we cannot scroll backward (we are the start of the list)
+                // We use graphicsLayer here to control the alpha so that we only redraw when this
+                // value changes, instead of recomposing
+                alpha = if (state.canScrollBackward) 1f else 0f
+            },
+            Color.Red
+        )
+        val items = (1..100).toList()
+        LazyColumn(
+            Modifier
+                .weight(1f)
+                .fillMaxWidth(), state
+        ) {
+            items(items) {
+                Text("Item is $it")
+            }
+        }
+        Icon(
+            Icons.Filled.KeyboardArrowDown,
+            null,
+            Modifier.graphicsLayer {
+                // Hide the icon if we cannot scroll forward (we are the end of the list)
+                // We use graphicsLayer here to control the alpha so that we only redraw when this
+                // value changes, instead of recomposing
+                alpha = if (state.canScrollForward) 1f else 0f
+            },
+            Color.Red
+        )
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableInScrollableViewGroupTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableInScrollableViewGroupTest.kt
index eb18a04..0ceccea 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableInScrollableViewGroupTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableInScrollableViewGroupTest.kt
@@ -37,9 +37,7 @@
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -205,10 +203,6 @@
     /**
      * Test case for a [clickable] inside an [AndroidView] inside a non-scrollable Compose container
      */
-    @Ignore(
-        "b/203141462 - currently this is not implemented so AndroidView()s will always " +
-            "appear scrollable"
-    )
     @Test
     fun clickable_androidViewInNotScrollableContainer() {
         val interactionSource = MutableInteractionSource()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt
index b05fcaa..d6563b6 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollTest.kt
@@ -60,7 +60,6 @@
 import androidx.compose.ui.layout.MeasurePolicy
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.LocalDensity
@@ -105,6 +104,7 @@
 import kotlinx.coroutines.launch
 import org.junit.After
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Rule
@@ -969,6 +969,87 @@
         assertThat(sizeParam).isEqualTo(Constraints.Infinity)
     }
 
+    @Test
+    fun canNotScrollForwardOrBackward() {
+        val scrollState = ScrollState(initial = 0)
+
+        composeScroller(scrollState)
+
+        rule.runOnIdle {
+            assertTrue(scrollState.maxValue == 0)
+            assertFalse(scrollState.canScrollForward)
+            assertFalse(scrollState.canScrollBackward)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    fun canScrollForward() {
+        val scrollState = ScrollState(initial = 0)
+        val size = 30
+
+        composeScroller(scrollState, mainAxisSize = size)
+
+        validateScroller(mainAxis = size)
+
+        rule.runOnIdle {
+            assertTrue(scrollState.value == 0)
+            assertTrue(scrollState.maxValue > 0)
+            assertTrue(scrollState.canScrollForward)
+            assertFalse(scrollState.canScrollBackward)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    fun canScrollBackward() {
+        val scrollState = ScrollState(initial = 0)
+        val scrollDistance = 10
+        val size = 30
+
+        composeScroller(scrollState, mainAxisSize = size)
+
+        validateScroller(mainAxis = size)
+
+        rule.waitForIdle()
+        assertEquals(scrollDistance, scrollState.maxValue)
+        scope.launch {
+            scrollState.scrollTo(scrollDistance)
+        }
+
+        rule.runOnIdle {
+            assertTrue(scrollState.value == scrollDistance)
+            assertTrue(scrollState.maxValue == scrollDistance)
+            assertFalse(scrollState.canScrollForward)
+            assertTrue(scrollState.canScrollBackward)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    fun canScrollForwardAndBackward() {
+        val scrollState = ScrollState(initial = 0)
+        val scrollDistance = 5
+        val size = 30
+
+        composeScroller(scrollState, mainAxisSize = size)
+
+        validateScroller(mainAxis = size)
+
+        rule.waitForIdle()
+        assertEquals(scrollDistance, scrollState.maxValue / 2)
+        scope.launch {
+            scrollState.scrollTo(scrollDistance)
+        }
+
+        rule.runOnIdle {
+            assertTrue(scrollState.value == scrollDistance)
+            assertTrue(scrollState.maxValue == scrollDistance * 2)
+            assertTrue(scrollState.canScrollForward)
+            assertTrue(scrollState.canScrollBackward)
+        }
+    }
+
     private fun Modifier.intrinsicMainAxisSize(size: IntrinsicSize): Modifier =
         if (config.orientation == Horizontal) {
             width(size)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 2b284d1..3ca102f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -22,7 +22,6 @@
 import androidx.compose.animation.rememberSplineBasedDecay
 import androidx.compose.foundation.gestures.DefaultFlingBehavior
 import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.gestures.ModifierLocalScrollableContainer
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.gestures.ScrollableDefaults
@@ -60,6 +59,7 @@
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -69,8 +69,6 @@
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.util.VelocityTracker
 import androidx.compose.ui.materialize
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalReadScope
 import androidx.compose.ui.platform.AbstractComposeView
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.LocalFocusManager
@@ -1689,49 +1687,73 @@
     fun scrollable_setsModifierLocalScrollableContainer() {
         val controller = ScrollableState { it }
 
-        var isOuterInScrollableContainer: Boolean? = null
-        var isInnerInScrollableContainer: Boolean? = null
+        var isOuterInVerticalScrollableContainer: Boolean? = null
+        var isInnerInVerticalScrollableContainer: Boolean? = null
+        var isOuterInHorizontalScrollableContainer: Boolean? = null
+        var isInnerInHorizontalScrollableContainer: Boolean? = null
         rule.setContent {
             Box {
                 Box(
                     modifier = Modifier
                         .testTag(scrollableBoxTag)
                         .size(100.dp)
-                        .then(
-                            object : ModifierLocalConsumer {
-                                override fun onModifierLocalsUpdated(
-                                    scope: ModifierLocalReadScope
-                                ) {
-                                    with(scope) {
-                                        isOuterInScrollableContainer =
-                                            ModifierLocalScrollableContainer.current
-                                    }
-                                }
-                            }
-                        )
+                        .consumeScrollContainerInfo {
+                            isOuterInVerticalScrollableContainer = it?.canScrollVertically()
+                            isOuterInHorizontalScrollableContainer = it?.canScrollHorizontally()
+                        }
                         .scrollable(
                             state = controller,
                             orientation = Orientation.Horizontal
                         )
-                        .then(
-                            object : ModifierLocalConsumer {
-                                override fun onModifierLocalsUpdated(
-                                    scope: ModifierLocalReadScope
-                                ) {
-                                    with(scope) {
-                                        isInnerInScrollableContainer =
-                                            ModifierLocalScrollableContainer.current
-                                    }
-                                }
-                            }
-                        )
+                        .consumeScrollContainerInfo {
+                            isInnerInHorizontalScrollableContainer = it?.canScrollHorizontally()
+                            isInnerInVerticalScrollableContainer = it?.canScrollVertically()
+                        }
                 )
             }
         }
 
         rule.runOnIdle {
-            assertThat(isOuterInScrollableContainer).isFalse()
-            assertThat(isInnerInScrollableContainer).isTrue()
+            assertThat(isInnerInHorizontalScrollableContainer).isTrue()
+            assertThat(isInnerInVerticalScrollableContainer).isFalse()
+            assertThat(isOuterInVerticalScrollableContainer).isFalse()
+            assertThat(isOuterInHorizontalScrollableContainer).isFalse()
+        }
+    }
+
+    @Test
+    fun scrollable_nested_setsModifierLocalScrollableContainer() {
+        val horizontalController = ScrollableState { it }
+        val verticalController = ScrollableState { it }
+
+        var horizontalDrag: Boolean? = null
+        var verticalDrag: Boolean? = null
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .size(100.dp)
+                    .scrollable(
+                        state = horizontalController,
+                        orientation = Orientation.Horizontal
+                    )
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(100.dp)
+                        .scrollable(
+                            state = verticalController,
+                            orientation = Orientation.Vertical
+                        )
+                        .consumeScrollContainerInfo {
+                            horizontalDrag = it?.canScrollHorizontally()
+                            verticalDrag = it?.canScrollVertically()
+                        })
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(horizontalDrag).isTrue()
+            assertThat(verticalDrag).isTrue()
         }
     }
 
@@ -2636,4 +2658,4 @@
     )
 }
 
-private class TestScrollMotionDurationScale(override val scaleFactor: Float) : MotionDurationScale
\ No newline at end of file
+internal class TestScrollMotionDurationScale(override val scaleFactor: Float) : MotionDurationScale
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/SnapFlingBehaviorTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/SnapFlingBehaviorTest.kt
index 6c5dffe..0c409ef 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/SnapFlingBehaviorTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/SnapFlingBehaviorTest.kt
@@ -28,6 +28,7 @@
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.TestScrollMotionDurationScale
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.rememberScrollableState
 import androidx.compose.foundation.gestures.snapping.MinFlingVelocityDp
@@ -36,15 +37,36 @@
 import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
 import androidx.compose.foundation.gestures.snapping.findClosestOffset
 import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+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.Modifier
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeWithVelocity
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
 import kotlin.test.assertEquals
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -173,6 +195,145 @@
             assertEquals(0, inspectTweenAnimationSpec.animationWasExecutions)
         }
     }
+
+    @Test
+    fun disableSystemAnimations_defaultFlingBehaviorShouldContinueToWork() {
+
+        lateinit var defaultFlingBehavior: SnapFlingBehavior
+        lateinit var scope: CoroutineScope
+        val state = LazyListState()
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            defaultFlingBehavior = rememberSnapFlingBehavior(state) as SnapFlingBehavior
+
+            LazyRow(
+                modifier = Modifier.fillMaxWidth(),
+                state = state,
+                flingBehavior = defaultFlingBehavior as FlingBehavior
+            ) {
+                items(200) { Box(modifier = Modifier.size(20.dp)) }
+            }
+        }
+
+        // Act: Stop clock and fling, one frame should not settle immediately.
+        rule.mainClock.autoAdvance = false
+        scope.launch {
+            state.scroll {
+                with(defaultFlingBehavior) { performFling(10000f) }
+            }
+        }
+        rule.mainClock.advanceTimeByFrame()
+
+        // Assert
+        rule.runOnIdle {
+            Truth.assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+
+        rule.mainClock.autoAdvance = true
+
+        val previousIndex = state.firstVisibleItemIndex
+
+        // Simulate turning off system wide animation
+        scope.launch {
+            state.scroll {
+                withContext(TestScrollMotionDurationScale(0f)) {
+                    with(defaultFlingBehavior) { performFling(10000f) }
+                }
+            }
+        }
+
+        // Act: Stop clock and fling, one frame should not settle immediately.
+        rule.mainClock.autoAdvance = false
+        scope.launch {
+            state.scroll {
+                with(defaultFlingBehavior) { performFling(10000f) }
+            }
+        }
+        rule.mainClock.advanceTimeByFrame()
+
+        // Assert
+        rule.runOnIdle {
+            Truth.assertThat(state.firstVisibleItemIndex).isEqualTo(previousIndex)
+        }
+
+        rule.mainClock.autoAdvance = true
+
+        // Assert: let it settle
+        rule.runOnIdle {
+            Truth.assertThat(state.firstVisibleItemIndex).isNotEqualTo(previousIndex)
+        }
+    }
+
+    @Test
+    fun defaultFlingBehavior_useScrollMotionDurationScale() {
+        // Arrange
+        var switchMotionDurationScale by mutableStateOf(false)
+        lateinit var defaultFlingBehavior: SnapFlingBehavior
+        lateinit var scope: CoroutineScope
+        val state = LazyListState()
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            defaultFlingBehavior = rememberSnapFlingBehavior(state) as SnapFlingBehavior
+
+            LazyRow(
+                modifier = Modifier
+                    .testTag("snappingList")
+                    .fillMaxSize(),
+                state = state,
+                flingBehavior = defaultFlingBehavior as FlingBehavior
+            ) {
+                items(200) {
+                    Box(modifier = Modifier.size(150.dp)) {
+                        BasicText(text = it.toString())
+                    }
+                }
+            }
+
+            if (switchMotionDurationScale) {
+                defaultFlingBehavior.motionScaleDuration = TestScrollMotionDurationScale(1f)
+            } else {
+                defaultFlingBehavior.motionScaleDuration = TestScrollMotionDurationScale(0f)
+            }
+        }
+
+        // Act: Stop clock and fling, one frame should settle immediately.
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag("snappingList").performTouchInput {
+            swipeWithVelocity(centerRight, center, 10000f)
+        }
+        rule.mainClock.advanceTimeByFrame()
+
+        // Assert
+        rule.runOnIdle {
+            Truth.assertThat(state.firstVisibleItemIndex).isGreaterThan(0)
+        }
+
+        // Arrange
+        rule.mainClock.autoAdvance = true
+        switchMotionDurationScale = true // Let animations run normally
+        rule.waitForIdle()
+
+        val previousIndex = state.firstVisibleItemIndex
+        // Act: Stop clock and fling, one frame should not settle.
+        rule.mainClock.autoAdvance = false
+        scope.launch {
+            state.scroll {
+                with(defaultFlingBehavior) { performFling(10000f) }
+            }
+        }
+
+        // Assert: First index hasn't changed because animation hasn't started
+        rule.mainClock.advanceTimeByFrame()
+        rule.runOnIdle {
+            Truth.assertThat(state.firstVisibleItemIndex).isEqualTo(previousIndex)
+        }
+        rule.mainClock.autoAdvance = true
+
+        // Wait for settling
+        rule.runOnIdle {
+            Truth.assertThat(state.firstVisibleItemIndex).isNotEqualTo(previousIndex)
+        }
+    }
 }
 
 @Composable
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
index b11236d..02475c7 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
@@ -1375,6 +1375,39 @@
         }
         rule.waitUntil(timeoutMillis = 10000) { animationFinished }
     }
+
+    @Test
+    fun fillingFullSize_nextItemIsNotComposed() {
+        val state = LazyGridState()
+        state.prefetchingEnabled = false
+        val itemSizePx = 5f
+        val itemSize = with(rule.density) { itemSizePx.toDp() }
+        rule.setContentWithTestViewConfiguration {
+            LazyGrid(
+                1,
+                Modifier
+                    .testTag(LazyGridTag)
+                    .mainAxisSize(itemSize),
+                state
+            ) {
+                items(3) { index ->
+                    Box(Modifier.size(itemSize).testTag("$index"))
+                }
+            }
+        }
+
+        repeat(3) { index ->
+            rule.onNodeWithTag("$index")
+                .assertIsDisplayed()
+            rule.onNodeWithTag("${index + 1}")
+                .assertDoesNotExist()
+            rule.runOnIdle {
+                runBlocking {
+                    state.scrollBy(itemSizePx)
+                }
+            }
+        }
+    }
 }
 
 internal fun IntegerSubject.isEqualTo(expected: Int, tolerance: Int) {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt
index c4bf183..50475ce 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridsContentPaddingTest.kt
@@ -442,7 +442,7 @@
             .assertTopPositionInRootIsEqualTo(0.dp)
         // Not visible.
         rule.onNodeWithTag("1")
-            .assertIsNotDisplayed()
+            .assertDoesNotExist()
 
         // Scroll to the top.
         state.scrollBy(itemSize * 5f)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollTest.kt
index 962cda5..5d6408a 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyScrollTest.kt
@@ -243,6 +243,33 @@
         assertSpringAnimation(toIndex = 0, toOffset = 40, fromIndex = 8)
     }
 
+    @Test
+    fun canScrollForward() = runBlocking {
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+        assertThat(state.canScrollForward).isTrue()
+        assertThat(state.canScrollBackward).isFalse()
+    }
+
+    @Test
+    fun canScrollBackward() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(itemsCount)
+        }
+        assertThat(state.firstVisibleItemIndex).isEqualTo(itemsCount - 6)
+        assertThat(state.canScrollForward).isFalse()
+        assertThat(state.canScrollBackward).isTrue()
+    }
+
+    @Test
+    fun canScrollForwardAndBackward() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(10)
+        }
+        assertThat(state.firstVisibleItemIndex).isEqualTo(10)
+        assertThat(state.canScrollForward).isTrue()
+        assertThat(state.canScrollBackward).isTrue()
+    }
+
     private fun assertSpringAnimation(
         toIndex: Int,
         toOffset: Int = 0,
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt
index ffc7d89..5580a92 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt
@@ -34,7 +34,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,7 +45,6 @@
     @get:Rule
     val rule = createComposeRule()
 
-    @Ignore // b/244308934
     @Test
     fun visibleItemsStateRestored() {
         val restorationTester = StateRestorationTester(rule)
@@ -83,7 +81,6 @@
         }
     }
 
-    @Ignore // b/244308934
     @Test
     fun itemsStateRestoredWhenWeScrolledBackToIt() {
         var counter0 = 1
@@ -123,7 +120,6 @@
         }
     }
 
-    @Ignore // b/244308934
     @Test
     fun nestedLazy_itemsStateRestoredWhenWeScrolledBackToIt() {
         var counter0 = 1
@@ -206,7 +202,6 @@
         }
     }
 
-    @Ignore // b/244308934
     @Test
     fun stateRestoredWhenUsedWithCustomKeysAfterReordering() {
         val restorationTester = StateRestorationTester(rule)
@@ -218,7 +213,7 @@
         restorationTester.setContent {
             LazyLayout(
                 itemCount = list.size,
-                indexToKey = { "${list[it]}" }
+                indexToKey = { "${list.getOrNull(it)}" }
             ) { index ->
                 val it = list[index]
                 if (it == 0) {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
index d183f39..8a426a4 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
@@ -58,6 +58,13 @@
             this.fillMaxHeight()
         }
 
+    fun LazyItemScope.fillParentMaxMainAxis() =
+        if (vertical) {
+            Modifier.fillParentMaxHeight()
+        } else {
+            Modifier.fillParentMaxWidth()
+        }
+
     fun LazyItemScope.fillParentMaxCrossAxis() =
         if (vertical) {
             Modifier.fillParentMaxWidth()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
index 3af1774..9838ba9 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
@@ -106,9 +106,8 @@
 
         // Assert.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'contains' with 'containsExactly'.
-            assertThat(placedItems).contains(0)
-            assertThat(visibleItems).contains(0)
+            assertThat(placedItems).containsExactly(0)
+            assertThat(visibleItems).containsExactly(0)
         }
     }
 
@@ -127,9 +126,8 @@
 
         // Assert.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(0, 1)
-            assertThat(visibleItems).containsAtLeast(0, 1)
+            assertThat(placedItems).containsExactly(0, 1)
+            assertThat(visibleItems).containsExactly(0, 1)
         }
     }
 
@@ -148,9 +146,8 @@
 
         // Assert.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(0, 1, 2)
-            assertThat(visibleItems).containsAtLeast(0, 1, 2)
+            assertThat(placedItems).containsExactly(0, 1, 2)
+            assertThat(visibleItems).containsExactly(0, 1, 2)
         }
     }
 
@@ -189,13 +186,11 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that the beyond bounds items are present.
                 if (expectedExtraItemsBeforeVisibleBounds()) {
-                    // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                    assertThat(placedItems).containsAtLeast(4, 5, 6, 7)
-                    assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                    assertThat(placedItems).containsExactly(4, 5, 6, 7)
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
                 } else {
-                    // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                    assertThat(placedItems).containsAtLeast(5, 6, 7, 8)
-                    assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                    assertThat(placedItems).containsExactly(5, 6, 7, 8)
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
                 placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
@@ -206,9 +201,8 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(5, 6, 7)
-            assertThat(visibleItems).containsAtLeast(5, 6, 7)
+            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
 
@@ -254,13 +248,11 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                        assertThat(placedItems).containsAtLeast(3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                        assertThat(placedItems).containsExactly(3, 4, 5, 6, 7)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
                     } else {
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                        assertThat(placedItems).containsAtLeast(5, 6, 7, 8, 9)
-                        assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
                     placedItems.clear()
                     // Return true to stop the search.
@@ -271,9 +263,8 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(5, 6, 7)
-            assertThat(visibleItems).containsAtLeast(5, 6, 7)
+            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
 
@@ -320,13 +311,11 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                        assertThat(placedItems).containsAtLeast(0, 1, 2, 3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                        assertThat(placedItems).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
                     } else {
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                        assertThat(placedItems).containsAtLeast(5, 6, 7, 8, 9, 10)
-                        assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9, 10)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
                     placedItems.clear()
                     // Return true to end the search.
@@ -337,8 +326,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(5, 6, 7)
+            assertThat(placedItems).containsExactly(5, 6, 7)
         }
     }
 
@@ -368,11 +356,15 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 5 }
+                        .onPlaced { placedItems += index + 6 }
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
+        rule.runOnIdle {
+            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
+            placedItems.clear()
+        }
 
         // Act.
         rule.runOnUiThread {
@@ -380,19 +372,16 @@
                 beyondBoundsLayoutCount++
                 when (beyondBoundsLayoutDirection) {
                     Left, Right, Above, Below -> {
-                        assertThat(placedItems).containsExactlyElementsIn(visibleItems)
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                        assertThat(placedItems).containsAtLeast(5, 6, 7)
-                        assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                        assertThat(placedItems).containsExactly(5, 6, 7)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
                     Before, After -> {
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
                         if (expectedExtraItemsBeforeVisibleBounds()) {
-                            assertThat(placedItems).containsAtLeast(4, 5, 6, 7)
-                            assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                            assertThat(placedItems).containsExactly(4, 5, 6, 7)
+                            assertThat(visibleItems).containsExactly(5, 6, 7)
                         } else {
-                            assertThat(placedItems).containsAtLeast(5, 6, 7, 8)
-                            assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                            assertThat(placedItems).containsExactly(5, 6, 7, 8)
+                            assertThat(visibleItems).containsExactly(5, 6, 7)
                         }
                     }
                 }
@@ -412,9 +401,8 @@
                     assertThat(beyondBoundsLayoutCount).isEqualTo(1)
 
                     // Assert that the beyond bounds items are removed.
-                    // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                    assertThat(placedItems).containsAtLeast(5, 6, 7)
-                    assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                    assertThat(placedItems).containsExactly(5, 6, 7)
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
                 else -> error("Unsupported BeyondBoundsLayoutDirection")
             }
@@ -470,9 +458,8 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(5, 6, 7)
-            assertThat(visibleItems).containsAtLeast(5, 6, 7)
+            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
index c24bfad..0e2d86f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
@@ -316,7 +316,7 @@
         }
         rule.runOnIdle {
             // Scroll so that the focused item is in the middle.
-            runBlocking { lazyListState.scrollToItem(1) }
+            runBlocking { lazyListState.scrollToItem(1, 10) }
             initiallyFocused.requestFocus()
 
             // Move focus to the last visible item.
@@ -370,7 +370,7 @@
         }
         rule.runOnIdle {
             // Scroll so that the focused item is in the middle.
-            runBlocking { lazyListState.scrollToItem(1) }
+            runBlocking { lazyListState.scrollToItem(2, 0) }
             initiallyFocused.requestFocus()
 
             // Move focus to the last visible item.
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
index 25e78a4..72f5302 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.VelocityTrackerCalculationThreshold
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxWithConstraints
 import androidx.compose.foundation.layout.Column
@@ -2146,6 +2147,67 @@
             .assertCrossAxisSizeIsEqualTo(0.dp)
     }
 
+    @Test
+    fun fillingFullSize_nextItemIsNotComposed() {
+        val state = LazyListState()
+        state.prefetchingEnabled = false
+        val itemSizePx = 5f
+        val itemSize = with(rule.density) { itemSizePx.toDp() }
+        rule.setContentWithTestViewConfiguration {
+            LazyColumnOrRow(
+                Modifier
+                    .testTag(LazyListTag)
+                    .mainAxisSize(itemSize),
+                state = state
+            ) {
+                items(3) { index ->
+                    Box(fillParentMaxMainAxis().crossAxisSize(1.dp).testTag("$index"))
+                }
+            }
+        }
+
+        repeat(3) { index ->
+            rule.onNodeWithTag("$index")
+                .assertIsDisplayed()
+            rule.onNodeWithTag("${index + 1}")
+                .assertDoesNotExist()
+            rule.runOnIdle {
+                runBlocking {
+                    state.scrollBy(itemSizePx)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun fillingFullSize_crossAxisSizeOfVisibleItemIsUsed() {
+        val state = LazyListState()
+        val itemSizePx = 5f
+        val itemSize = with(rule.density) { itemSizePx.toDp() }
+        rule.setContentWithTestViewConfiguration {
+            LazyColumnOrRow(
+                Modifier
+                    .testTag(LazyListTag)
+                    .mainAxisSize(itemSize),
+                state = state
+            ) {
+                items(5) { index ->
+                    Box(fillParentMaxMainAxis().crossAxisSize(index.dp))
+                }
+            }
+        }
+
+        repeat(5) { index ->
+            rule.onNodeWithTag(LazyListTag)
+                .assertCrossAxisSizeIsEqualTo(index.dp)
+            rule.runOnIdle {
+                runBlocking {
+                    state.scrollBy(itemSizePx)
+                }
+            }
+        }
+    }
+
     // ********************* END OF TESTS *********************
     // Helper functions, etc. live below here
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt
index 8b7a6ce..c46bf7d 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsContentPaddingTest.kt
@@ -418,7 +418,7 @@
         rule.onNodeWithTag("0")
             .assertStartPositionInRootIsEqualTo(0.dp)
         // Shouldn't be visible
-        rule.onNodeWithTag("1").assertIsNotDisplayed()
+        rule.onNodeWithTag("1").assertDoesNotExist()
 
         // Scroll to the top.
         state.scrollBy(itemSize * 5f)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollTest.kt
index 22da052..e0fdf831 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyScrollTest.kt
@@ -243,6 +243,33 @@
         assertSpringAnimation(toIndex = 0, toOffset = 20, fromIndex = 8)
     }
 
+    @Test
+    fun canScrollForward() = runBlocking {
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+        assertThat(state.canScrollForward).isTrue()
+        assertThat(state.canScrollBackward).isFalse()
+    }
+
+    @Test
+    fun canScrollBackward() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(itemsCount)
+        }
+        assertThat(state.firstVisibleItemIndex).isEqualTo(itemsCount - 3)
+        assertThat(state.canScrollForward).isFalse()
+        assertThat(state.canScrollBackward).isTrue()
+    }
+
+    @Test
+    fun canScrollForwardAndBackward() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(1)
+        }
+        assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        assertThat(state.canScrollForward).isTrue()
+        assertThat(state.canScrollBackward).isTrue()
+    }
+
     private fun assertSpringAnimation(
         toIndex: Int,
         toOffset: Int = 0,
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
index 10040bc..49c2a4e 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
@@ -33,6 +33,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -170,6 +171,39 @@
         assertThat(mainAxisOffset).isEqualTo(itemSizePx * 3) // x5 (grid) - x2 (item)
     }
 
+    @Test
+    fun canScrollForward() = runBlocking {
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+        assertThat(state.canScrollForward).isTrue()
+        assertThat(state.canScrollBackward).isFalse()
+    }
+
+    @Test
+    fun canScrollBackward() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(99)
+        }
+        val lastItem = state.layoutInfo.visibleItemsInfo.last()
+        val mainAxisOffset = if (orientation == Orientation.Vertical) {
+            lastItem.offset.y
+        } else {
+            lastItem.offset.x
+        }
+        assertThat(mainAxisOffset).isEqualTo(itemSizePx * 3) // x5 (grid) - x2 (item)
+        assertThat(state.canScrollForward).isFalse()
+        assertThat(state.canScrollBackward).isTrue()
+    }
+
+    @Test
+    fun canScrollForwardAndBackward() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(10)
+        }
+        assertThat(state.firstVisibleItemIndex).isEqualTo(10)
+        assertThat(state.canScrollForward).isTrue()
+        assertThat(state.canScrollBackward).isTrue()
+    }
+
     @Composable
     private fun TestContent() {
         // |-|-|
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
index fc86b6b..a9dc3a2 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
@@ -19,9 +19,11 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.border
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.list.setContentWithTestViewConfiguration
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -258,7 +260,7 @@
         }
 
         rule.onNodeWithTag("3")
-            .assertIsNotDisplayed()
+            .assertDoesNotExist()
 
         state.scrollBy(itemSizeDp * 3)
 
@@ -1210,4 +1212,37 @@
         rule.waitForIdle()
         assertThat(state.measurePassCount).isEqualTo(1)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun fillingFullSize_nextItemIsNotComposed() {
+        val state = LazyStaggeredGridState()
+        state.prefetchingEnabled = false
+        val itemSizePx = 5f
+        val itemSize = with(rule.density) { itemSizePx.toDp() }
+        rule.setContentWithTestViewConfiguration {
+            LazyStaggeredGrid(
+                1,
+                Modifier
+                    .testTag(LazyStaggeredGridTag)
+                    .mainAxisSize(itemSize),
+                state
+            ) {
+                items(3) { index ->
+                    Box(Modifier.size(itemSize).testTag("$index"))
+                }
+            }
+        }
+
+        repeat(3) { index ->
+            rule.onNodeWithTag("$index")
+                .assertIsDisplayed()
+            rule.onNodeWithTag("${index + 1}")
+                .assertDoesNotExist()
+            rule.runOnIdle {
+                runBlocking {
+                    state.scrollBy(itemSizePx)
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldKeyboardScrollableInteractionTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldKeyboardScrollableInteractionTest.kt
index 421bdef..019a837 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldKeyboardScrollableInteractionTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldKeyboardScrollableInteractionTest.kt
@@ -45,7 +45,6 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.click
@@ -55,6 +54,7 @@
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
 import org.junit.Assume.assumeTrue
 import org.junit.Rule
 import org.junit.Test
@@ -62,6 +62,7 @@
 import org.junit.runners.Parameterized
 import org.junit.runners.Parameterized.Parameters
 
+@MediumTest
 @RunWith(Parameterized::class)
 class CoreTextFieldKeyboardScrollableInteractionTest(
     private val scrollableType: ScrollableType,
@@ -96,11 +97,11 @@
 
     @Test
     fun test() {
-        // TODO(b/192043120) This is known broken when soft input mode is Resize.
-        assumeTrue(softInputMode != AdjustResize)
+        // TODO(b/179203700) This is known broken for lazy lists.
+        assumeTrue(scrollableType != LazyList)
 
         rule.setContent {
-            keyboardHelper.view = LocalView.current
+            keyboardHelper.initialize()
             TestContent()
         }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt
index 630e70e..a9b6fe6 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt
@@ -24,19 +24,18 @@
 import androidx.compose.ui.focus.focusProperties
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performImeAction
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.ImeAction.Companion.Done
 import androidx.compose.ui.text.input.ImeAction.Companion.Go
+import androidx.compose.ui.text.input.ImeAction.Companion.Next
+import androidx.compose.ui.text.input.ImeAction.Companion.Previous
 import androidx.compose.ui.text.input.ImeAction.Companion.Search
 import androidx.compose.ui.text.input.ImeAction.Companion.Send
-import androidx.compose.ui.text.input.ImeAction.Companion.Previous
-import androidx.compose.ui.text.input.ImeAction.Companion.Next
-import androidx.compose.ui.text.input.ImeAction.Companion.Done
-import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.ImeOptions
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.test.filters.LargeTest
@@ -82,8 +81,7 @@
         val keyboardHelper = KeyboardHelper(rule)
 
         rule.setContent {
-            keyboardHelper.view = LocalView.current
-
+            keyboardHelper.initialize()
             Column {
                 CoreTextField(
                     value = value1,
@@ -164,8 +162,7 @@
         val keyboardHelper = KeyboardHelper(rule)
 
         rule.setContent {
-            keyboardHelper.view = LocalView.current
-
+            keyboardHelper.initialize()
             Column {
                 CoreTextField(
                     value = value1,
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/KeyboardActionsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/KeyboardActionsTest.kt
index 9d52ba0..0418c0e 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/KeyboardActionsTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/KeyboardActionsTest.kt
@@ -18,24 +18,23 @@
 
 import android.os.Build
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performImeAction
 import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.ImeAction.Companion.Done
 import androidx.compose.ui.text.input.ImeAction.Companion.Go
+import androidx.compose.ui.text.input.ImeAction.Companion.Next
+import androidx.compose.ui.text.input.ImeAction.Companion.Previous
 import androidx.compose.ui.text.input.ImeAction.Companion.Search
 import androidx.compose.ui.text.input.ImeAction.Companion.Send
-import androidx.compose.ui.text.input.ImeAction.Companion.Previous
-import androidx.compose.ui.text.input.ImeAction.Companion.Next
-import androidx.compose.ui.text.input.ImeAction.Companion.Done
 import androidx.compose.ui.text.input.ImeOptions
 import androidx.compose.ui.text.input.TextFieldValue
-import com.google.common.truth.Truth.assertThat
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -113,8 +112,7 @@
         var wasCallbackTriggered = false
 
         rule.setContent {
-            keyboardHelper.view = LocalView.current
-
+            keyboardHelper.initialize()
             CoreTextField(
                 value = textFieldValue,
                 onValueChange = {},
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/KeyboardHelper.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/KeyboardHelper.kt
index 018cd82..f222389 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/KeyboardHelper.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/KeyboardHelper.kt
@@ -16,43 +16,60 @@
 
 package androidx.compose.foundation.text
 
+import android.app.Activity
 import android.content.Context
+import android.content.ContextWrapper
 import android.os.Build
 import android.view.View
+import android.view.Window
 import android.view.WindowInsets
 import android.view.WindowInsetsAnimation
 import android.view.inputmethod.InputMethodManager
 import androidx.annotation.RequiresApi
-import androidx.compose.ui.test.junit4.ComposeTestRule
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.window.DialogWindowProvider
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 
 /**
  * Helper methods for hiding and showing the keyboard in tests.
- * Must set [view] before calling any methods on this class.
+ * Call [initialize] from your test rule's content before calling any other methods on this class.
  */
+@OptIn(ExperimentalComposeUiApi::class)
 class KeyboardHelper(
-    private val composeRule: ComposeTestRule,
+    private val composeRule: ComposeContentTestRule,
     private val timeout: Long = 15_000L
 ) {
     /**
      * The [View] hosting the compose rule's content. Must be set before calling any methods on this
      * class.
      */
-    lateinit var view: View
+    private lateinit var view: View
     private val imm by lazy {
         view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
     }
 
     /**
+     * Call this at the top of your test composition before using the helper.
+     */
+    @Composable
+    fun initialize() {
+        view = LocalView.current
+    }
+
+    /**
      * Requests the keyboard to be hidden without waiting for it.
-     * Should be called from the main thread.
      */
     fun hideKeyboard() {
-        if (Build.VERSION.SDK_INT >= 30) {
+        composeRule.runOnIdle {
+            // Use both techniques to hide it, at least one of them will hopefully work.
             hideKeyboardWithInsets()
-        } else {
             hideKeyboardWithImm()
         }
     }
@@ -68,26 +85,25 @@
     }
 
     fun hideKeyboardIfShown() {
-        composeRule.runOnIdle {
-            if (isSoftwareKeyboardShown()) {
-                hideKeyboard()
-                waitForKeyboardVisibility(visible = false)
-            }
+        if (composeRule.runOnIdle { isSoftwareKeyboardShown() }) {
+            hideKeyboard()
+            waitForKeyboardVisibility(visible = false)
         }
     }
 
     fun isSoftwareKeyboardShown(): Boolean {
-        return if (Build.VERSION.SDK_INT >= 30) {
+        return if (Build.VERSION.SDK_INT >= 23) {
             isSoftwareKeyboardShownWithInsets()
         } else {
             isSoftwareKeyboardShownWithImm()
         }
     }
 
-    @RequiresApi(30)
+    @RequiresApi(23)
     private fun isSoftwareKeyboardShownWithInsets(): Boolean {
-        return view.rootWindowInsets != null &&
-            view.rootWindowInsets.isVisible(WindowInsets.Type.ime())
+        val insets = view.rootWindowInsets ?: return false
+        val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, view)
+        return insetsCompat.isVisible(WindowInsetsCompat.Type.ime())
     }
 
     private fun isSoftwareKeyboardShownWithImm(): Boolean {
@@ -97,35 +113,61 @@
     }
 
     private fun hideKeyboardWithImm() {
-        imm.hideSoftInputFromWindow(view.windowToken, 0)
+        view.post {
+            imm.hideSoftInputFromWindow(view.windowToken, 0)
+        }
     }
 
-    @RequiresApi(30)
     private fun hideKeyboardWithInsets() {
-        view.windowInsetsController?.hide(WindowInsets.Type.ime())
+        view.findWindow()?.let { WindowInsetsControllerCompat(it, view) }
+            ?.hide(WindowInsetsCompat.Type.ime())
     }
 
     private fun waitUntil(timeout: Long, condition: () -> Boolean) {
         if (Build.VERSION.SDK_INT >= 30) {
-            view.waitUntil(timeout, condition)
+            view.waitForWindowInsetsUntil(timeout, condition)
         } else {
             composeRule.waitUntil(timeout, condition)
         }
     }
-}
 
-@RequiresApi(30)
-fun View.waitUntil(timeoutMillis: Long, condition: () -> Boolean) {
-    val latch = CountDownLatch(1)
-    rootView.setWindowInsetsAnimationCallback(
-        InsetAnimationCallback {
+    // TODO(b/221889664) Replace with composition local when available.
+    private fun View.findWindow(): Window? =
+        (parent as? DialogWindowProvider)?.window
+            ?: context.findWindow()
+
+    private tailrec fun Context.findWindow(): Window? =
+        when (this) {
+            is Activity -> window
+            is ContextWrapper -> baseContext.findWindow()
+            else -> null
+        }
+
+    @RequiresApi(30)
+    fun View.waitForWindowInsetsUntil(timeoutMillis: Long, condition: () -> Boolean) {
+        val latch = CountDownLatch(1)
+        rootView.setOnApplyWindowInsetsListener { view, windowInsets ->
             if (condition()) {
                 latch.countDown()
             }
+            view.onApplyWindowInsets(windowInsets)
+            windowInsets
         }
-    )
-    val conditionMet = latch.await(timeoutMillis, TimeUnit.MILLISECONDS)
-    assertThat(conditionMet).isTrue()
+        rootView.setWindowInsetsAnimationCallback(
+            InsetAnimationCallback {
+                if (condition()) {
+                    latch.countDown()
+                }
+            }
+        )
+
+        // if condition already met return
+        if (condition()) return
+
+        // else wait for condition to be met
+        val conditionMet = latch.await(timeoutMillis, TimeUnit.MILLISECONDS)
+        assertThat(conditionMet).isTrue()
+    }
 }
 
 @RequiresApi(30)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
index 25a01d3..e9a3958 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
@@ -25,8 +25,11 @@
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.text.CoreTextField
+import androidx.compose.foundation.text.KeyboardHelper
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
@@ -43,8 +46,10 @@
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.window.Dialog
 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 org.junit.Rule
 import org.junit.Test
@@ -113,7 +118,7 @@
     }
 
     @Test
-    fun noCrushWhenSwitchingBetweenEnabledFocusedAndDisabledTextField() {
+    fun noCrashWhenSwitchingBetweenEnabledFocusedAndDisabledTextField() {
         val enabled = mutableStateOf(true)
         var focused = false
         val tag = "textField"
@@ -122,9 +127,11 @@
                 value = TextFieldValue(),
                 onValueChange = {},
                 enabled = enabled.value,
-                modifier = Modifier.testTag(tag).onFocusChanged {
-                    focused = it.isFocused
-                }
+                modifier = Modifier
+                    .testTag(tag)
+                    .onFocusChanged {
+                        focused = it.isFocused
+                    }
             )
         }
         // bring enabled text field into focus
@@ -198,4 +205,95 @@
             ).isEqualTo(innerCoordinates!!.size.toSize())
         }
     }
+
+    @Test
+    fun keyboardIsShown_forFieldInActivity_whenFocusRequestedImmediately_fromLaunchedEffect() {
+        keyboardIsShown_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                LaunchedEffect(Unit) {
+                    it()
+                }
+            }
+        )
+    }
+
+    @Test
+    fun keyboardIsShown_forFieldInActivity_whenFocusRequestedImmediately_fromDisposableEffect() {
+        keyboardIsShown_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                DisposableEffect(Unit) {
+                    it()
+                    onDispose {}
+                }
+            }
+        )
+    }
+
+    // TODO(b/229378542) We can't accurately detect IME visibility from dialogs before API 30 so
+    //  this test can't assert.
+    @SdkSuppress(minSdkVersion = 30)
+    @Test
+    fun keyboardIsShown_forFieldInDialog_whenFocusRequestedImmediately_fromLaunchedEffect() {
+        keyboardIsShown_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                LaunchedEffect(Unit) {
+                    it()
+                }
+            },
+            wrapContent = {
+                Dialog(onDismissRequest = {}, content = it)
+            }
+        )
+    }
+
+    // TODO(b/229378542) We can't accurately detect IME visibility from dialogs before API 30 so
+    //  this test can't assert.
+    @SdkSuppress(minSdkVersion = 30)
+    @Test
+    fun keyboardIsShown_forFieldInDialog_whenFocusRequestedImmediately_fromDisposableEffect() {
+        keyboardIsShown_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                DisposableEffect(Unit) {
+                    it()
+                    onDispose {}
+                }
+            },
+            wrapContent = {
+                Dialog(onDismissRequest = {}, content = it)
+            }
+        )
+    }
+
+    private fun keyboardIsShown_whenFocusRequestedImmediately_fromEffect(
+        runEffect: @Composable (body: () -> Unit) -> Unit,
+        wrapContent: @Composable (@Composable () -> Unit) -> Unit = { it() }
+    ) {
+        val focusRequester = FocusRequester()
+        val keyboardHelper = KeyboardHelper(rule)
+
+        rule.setContent {
+            wrapContent {
+                keyboardHelper.initialize()
+
+                runEffect {
+                    assertThat(keyboardHelper.isSoftwareKeyboardShown()).isFalse()
+                    focusRequester.requestFocus()
+                }
+
+                BasicTextField(
+                    value = "",
+                    onValueChange = {},
+                    modifier = Modifier.focusRequester(focusRequester)
+                )
+            }
+        }
+
+        keyboardHelper.waitForKeyboardVisibility(visible = true)
+
+        // Ensure the keyboard doesn't leak in to the next test. Can't do this at the start of the
+        // test since the KeyboardHelper won't be initialized until composition runs, and this test
+        // is checking behavior that all happens on the first frame.
+        keyboardHelper.hideKeyboard()
+        keyboardHelper.waitForKeyboardVisibility(visible = false)
+    }
 }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt
index 6b5cef6..79247de 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt
@@ -19,37 +19,13 @@
 import android.view.KeyEvent.KEYCODE_DPAD_CENTER
 import android.view.KeyEvent.KEYCODE_ENTER
 import android.view.KeyEvent.KEYCODE_NUMPAD_ENTER
-import android.view.View
 import android.view.ViewConfiguration
-import android.view.ViewGroup
-import androidx.compose.runtime.Composable
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
 import androidx.compose.ui.input.key.key
 import androidx.compose.ui.input.key.nativeKeyCode
 import androidx.compose.ui.input.key.type
-import androidx.compose.ui.platform.LocalView
-
-@Composable
-internal actual fun isComposeRootInScrollableContainer(): () -> Boolean {
-    val view = LocalView.current
-    return {
-        view.isInScrollableViewGroup()
-    }
-}
-
-// Copied from View#isInScrollingContainer() which is @hide
-private fun View.isInScrollableViewGroup(): Boolean {
-    var p = parent
-    while (p != null && p is ViewGroup) {
-        if (p.shouldDelayChildPressedState()) {
-            return true
-        }
-        p = p.parent
-    }
-    return false
-}
 
 internal actual val TapIndicationDelay: Long = ViewConfiguration.getTapTimeout().toLong()
 
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 b249514..64643f1 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
@@ -16,7 +16,6 @@
 
 package androidx.compose.foundation
 
-import androidx.compose.foundation.gestures.ModifierLocalScrollableContainer
 import androidx.compose.foundation.gestures.PressGestureScope
 import androidx.compose.foundation.gestures.detectTapAndPress
 import androidx.compose.foundation.gestures.detectTapGestures
@@ -38,8 +37,8 @@
 import androidx.compose.ui.input.key.key
 import androidx.compose.ui.input.key.onKeyEvent
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalReadScope
+import androidx.compose.ui.input.canScroll
+import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.disabled
@@ -144,11 +143,8 @@
                 currentKeyPressInteractions
             )
         }
-        val isRootInScrollableContainer = isComposeRootInScrollableContainer()
-        val isClickableInScrollableContainer = remember { mutableStateOf(true) }
-        val delayPressInteraction = rememberUpdatedState {
-            isClickableInScrollableContainer.value || isRootInScrollableContainer()
-        }
+
+        val delayPressInteraction = remember { mutableStateOf({ true }) }
         val centreOffset = remember { mutableStateOf(Offset.Zero) }
 
         val gesture = Modifier.pointerInput(interactionSource, enabled) {
@@ -168,18 +164,9 @@
             )
         }
         Modifier
-            .then(
-                remember {
-                    object : ModifierLocalConsumer {
-                        override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
-                            with(scope) {
-                                isClickableInScrollableContainer.value =
-                                    ModifierLocalScrollableContainer.current
-                            }
-                        }
-                    }
-                }
-            )
+            .consumeScrollContainerInfo { scrollContainerInfo ->
+                delayPressInteraction.value = { scrollContainerInfo?.canScroll() == true }
+            }
             .genericClickableWithoutGesture(
                 gestureModifiers = gesture,
                 interactionSource = interactionSource,
@@ -330,13 +317,9 @@
                 currentKeyPressInteractions
             )
         }
-        val isRootInScrollableContainer = isComposeRootInScrollableContainer()
-        val isClickableInScrollableContainer = remember { mutableStateOf(true) }
-        val delayPressInteraction = rememberUpdatedState {
-            isClickableInScrollableContainer.value || isRootInScrollableContainer()
-        }
-        val centreOffset = remember { mutableStateOf(Offset.Zero) }
 
+        val delayPressInteraction = remember { mutableStateOf({ true }) }
+        val centreOffset = remember { mutableStateOf(Offset.Zero) }
         val gesture =
             Modifier.pointerInput(interactionSource, hasLongClick, hasDoubleClick, enabled) {
                 centreOffset.value = size.center.toOffset()
@@ -365,18 +348,6 @@
                 )
             }
         Modifier
-            .then(
-                remember {
-                    object : ModifierLocalConsumer {
-                        override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
-                            with(scope) {
-                                isClickableInScrollableContainer.value =
-                                    ModifierLocalScrollableContainer.current
-                            }
-                        }
-                    }
-                }
-            )
             .genericClickableWithoutGesture(
                 gestureModifiers = gesture,
                 interactionSource = interactionSource,
@@ -391,6 +362,9 @@
                 onLongClick = onLongClick,
                 onClick = onClick
             )
+            .consumeScrollContainerInfo { scrollContainerInfo ->
+                delayPressInteraction.value = { scrollContainerInfo?.canScroll() == true }
+            }
     },
     inspectorInfo = debugInspectorInfo {
         name = "combinedClickable"
@@ -475,20 +449,6 @@
 internal expect val TapIndicationDelay: Long
 
 /**
- * Returns a lambda that calculates whether the root Compose layout node is hosted in a scrollable
- * container outside of Compose. On Android this will be whether the root View is in a scrollable
- * ViewGroup, as even if nothing in the Compose part of the hierarchy is scrollable, if the View
- * itself is in a scrollable container, we still want to delay presses in case presses in Compose
- * convert to a scroll outside of Compose.
- *
- * Combine this with [ModifierLocalScrollableContainer], which returns whether a [Modifier] is
- * within a scrollable Compose layout, to calculate whether this modifier is within some form of
- * scrollable container, and hence should delay presses.
- */
-@Composable
-internal expect fun isComposeRootInScrollableContainer(): () -> Boolean
-
-/**
  * Whether the specified [KeyEvent] should trigger a press for a clickable component.
  */
 internal expect val KeyEvent.isPress: Boolean
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
index 18ca13c..9c1374a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
@@ -30,6 +30,7 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -148,6 +149,10 @@
     override val isScrollInProgress: Boolean
         get() = scrollableState.isScrollInProgress
 
+    override val canScrollForward: Boolean by derivedStateOf { value < maxValue }
+
+    override val canScrollBackward: Boolean by derivedStateOf { value > 0 }
+
     /**
      * Scroll to position in pixels with animation.
      *
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
index 6507207..161c854 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
@@ -24,6 +24,7 @@
 import androidx.compose.foundation.gestures.DragEvent.DragStopped
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.internal.JvmDefaultWithCompatibility
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
@@ -52,7 +53,6 @@
 import kotlinx.coroutines.channels.SendChannel
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.isActive
-import androidx.compose.foundation.internal.JvmDefaultWithCompatibility
 
 /**
  * State of [draggable]. Allows for a granular control of how deltas are consumed by the user as
@@ -255,6 +255,7 @@
             }
         }
     }
+
     Modifier.pointerInput(orientation, enabled, reverseDirection) {
         if (!enabled) return@pointerInput
         coroutineScope {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 7aac595..7fa7897 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -37,6 +37,7 @@
 import androidx.compose.ui.MotionDurationScale
 import androidx.compose.ui.composed
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.ScrollContainerInfo
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -48,8 +49,7 @@
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.modifier.ModifierLocalProvider
-import androidx.compose.ui.modifier.modifierLocalOf
+import androidx.compose.ui.input.provideScrollContainerInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.Density
@@ -172,7 +172,6 @@
                 overscrollEffect,
                 enabled
             )
-            .then(if (enabled) ModifierLocalScrollableContainerProvider else Modifier)
     }
 )
 
@@ -266,6 +265,14 @@
     val draggableState = remember { ScrollDraggableState(scrollLogic) }
     val scrollConfig = platformScrollConfig()
 
+    val scrollContainerInfo = remember(orientation, enabled) {
+        object : ScrollContainerInfo {
+            override fun canScrollHorizontally() = enabled && orientation == Horizontal
+
+            override fun canScrollVertically() = enabled && orientation == Orientation.Vertical
+        }
+    }
+
     return draggable(
         draggableState,
         orientation = orientation,
@@ -282,6 +289,7 @@
     )
         .mouseWheelScroll(scrollLogic, scrollConfig)
         .nestedScroll(nestedScrollConnection, nestedScrollDispatcher.value)
+        .provideScrollContainerInfo(scrollContainerInfo)
 }
 
 private fun Modifier.mouseWheelScroll(
@@ -580,21 +588,9 @@
     }
 }
 
-// TODO: b/203141462 - make this public and move it to ui
-/**
- * Whether this modifier is inside a scrollable container, provided by [Modifier.scrollable].
- * Defaults to false.
- */
-internal val ModifierLocalScrollableContainer = modifierLocalOf { false }
-
-private object ModifierLocalScrollableContainerProvider : ModifierLocalProvider<Boolean> {
-    override val key = ModifierLocalScrollableContainer
-    override val value = true
-}
-
 private const val DefaultScrollMotionDurationScaleFactor = 1f
 
-private val DefaultScrollMotionDurationScale = object : MotionDurationScale {
+internal val DefaultScrollMotionDurationScale = object : MotionDurationScale {
     override val scaleFactor: Float
         get() = DefaultScrollMotionDurationScaleFactor
-}
\ No newline at end of file
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ScrollableState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ScrollableState.kt
index 9d469a4..9bb6e31 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ScrollableState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ScrollableState.kt
@@ -78,6 +78,34 @@
      * not.
      */
     val isScrollInProgress: Boolean
+
+    /**
+     * Whether this [ScrollableState] can scroll forward (consume a positive delta). This is
+     * typically false if the scroll position is equal to its maximum value, and true otherwise.
+     *
+     * Note that `true` here does not imply that delta *will* be consumed - the ScrollableState may
+     * decide not to handle the incoming delta (such as if it is already being scrolled separately).
+     * Additionally, for backwards compatibility with previous versions of ScrollableState this
+     * value defaults to `true`.
+     *
+     * @sample androidx.compose.foundation.samples.CanScrollSample
+     */
+    val canScrollForward: Boolean
+        get() = true
+
+    /**
+     * Whether this [ScrollableState] can scroll backward (consume a negative delta). This is
+     * typically false if the scroll position is equal to its minimum value, and true otherwise.
+     *
+     * Note that `true` here does not imply that delta *will* be consumed - the ScrollableState may
+     * decide not to handle the incoming delta (such as if it is already being scrolled separately).
+     * Additionally, for backwards compatibility with previous versions of ScrollableState this
+     * value defaults to `true`.
+     *
+     * @sample androidx.compose.foundation.samples.CanScrollSample
+     */
+    val canScrollBackward: Boolean
+        get() = true
 }
 
 /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
index 9d632b2..b1e05f7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
@@ -32,6 +32,7 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.rememberSplineBasedDecay
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.DefaultScrollMotionDurationScale
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.runtime.Composable
@@ -43,6 +44,7 @@
 import kotlin.math.abs
 import kotlin.math.absoluteValue
 import kotlin.math.sign
+import kotlinx.coroutines.withContext
 
 /**
  * A [FlingBehavior] that performs snapping of items to a given position. The algorithm will
@@ -89,13 +91,16 @@
 ) : FlingBehavior {
 
     private val velocityThreshold = with(density) { shortSnapVelocityThreshold.toPx() }
+    internal var motionScaleDuration = DefaultScrollMotionDurationScale
 
     override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
         // If snapping from scroll (short snap) or fling (long snap)
-        if (abs(initialVelocity) <= abs(velocityThreshold)) {
-            shortSnap(initialVelocity)
-        } else {
-            longSnap(initialVelocity)
+        withContext(motionScaleDuration) {
+            if (abs(initialVelocity) <= abs(velocityThreshold)) {
+                shortSnap(initialVelocity)
+            } else {
+                longSnap(initialVelocity)
+            }
         }
         return NoVelocity
     }
@@ -399,7 +404,7 @@
                 targetOffset = targetOffset,
                 cancelOffset = offset,
                 animationState = animationState,
-                snapAnimationSpec = lowVelocityAnimationSpec,
+                snapAnimationSpec = lowVelocityAnimationSpec
             )
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index 4665785..cc6ac54 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -149,8 +149,10 @@
         // then composing visible items forward until we fill the whole viewport.
         // we want to have at least one item in visibleItems even if in fact all the items are
         // offscreen, this can happen if the content padding is larger than the available size.
-        while ((currentMainAxisOffset <= maxMainAxis || visibleItems.isEmpty()) &&
-            index.value < itemsCount
+        while (index.value < itemsCount &&
+            (currentMainAxisOffset < maxMainAxis ||
+                currentMainAxisOffset <= 0 || // filling beforeContentPadding area
+                visibleItems.isEmpty())
         ) {
             val measuredItem = itemProvider.getAndMeasure(index)
             currentMainAxisOffset += measuredItem.sizeWithSpacings
@@ -302,7 +304,7 @@
         return LazyListMeasureResult(
             firstVisibleItem = firstItem,
             firstVisibleItemScrollOffset = currentFirstItemScrollOffset,
-            canScrollForward = currentMainAxisOffset > maxOffset,
+            canScrollForward = index.value < itemsCount || currentMainAxisOffset > maxOffset,
             consumedScroll = consumedScroll,
             measureResult = layout(layoutWidth, layoutHeight) {
                 positionedItems.fastForEach {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index 09ddf10..cb7e890 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -268,8 +268,9 @@
     override val isScrollInProgress: Boolean
         get() = scrollableState.isScrollInProgress
 
-    private var canScrollBackward: Boolean = false
-    internal var canScrollForward: Boolean = false
+    override var canScrollForward: Boolean by mutableStateOf(false)
+        private set
+    override var canScrollBackward: Boolean by mutableStateOf(false)
         private set
 
     // TODO: Coroutine scrolling APIs will allow this to be private again once we have more
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
index b1ebdd0..5eb6c73 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
@@ -139,7 +139,11 @@
         // then composing visible lines forward until we fill the whole viewport.
         // we want to have at least one line in visibleItems even if in fact all the items are
         // offscreen, this can happen if the content padding is larger than the available size.
-        while (currentMainAxisOffset <= maxMainAxis || visibleLines.isEmpty()) {
+        while (index.value < itemsCount &&
+            (currentMainAxisOffset < maxMainAxis ||
+                currentMainAxisOffset <= 0 || // filling beforeContentPadding area
+                visibleLines.isEmpty())
+        ) {
             val measuredLine = measuredLineProvider.getAndMeasure(index)
             if (measuredLine.isEmpty()) {
                 --index
@@ -251,7 +255,7 @@
         return LazyGridMeasureResult(
             firstVisibleLine = firstLine,
             firstVisibleLineScrollOffset = currentFirstLineScrollOffset,
-            canScrollForward = currentMainAxisOffset > maxOffset,
+            canScrollForward = index.value < itemsCount || currentMainAxisOffset > maxOffset,
             consumedScroll = consumedScroll,
             measureResult = layout(layoutWidth, layoutHeight) {
                 positionedItems.fastForEach { it.place(this) }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
index 428f04e..a19e3d2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
@@ -273,8 +273,9 @@
     override val isScrollInProgress: Boolean
         get() = scrollableState.isScrollInProgress
 
-    private var canScrollBackward: Boolean = false
-    internal var canScrollForward: Boolean = false
+    override var canScrollForward: Boolean by mutableStateOf(false)
+        private set
+    override var canScrollBackward: Boolean by mutableStateOf(false)
         private set
 
     // TODO: Coroutine scrolling APIs will allow this to be private again once we have more
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
index 511da2d0..e0f601d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
@@ -333,7 +333,10 @@
         // we want to have at least one item in visibleItems even if in fact all the items are
         // offscreen, this can happen if the content padding is larger than the available size.
         while (
-            currentItemOffsets.any { it <= maxOffset } || measuredItems.all { it.isEmpty() }
+            currentItemOffsets.any {
+                it < maxOffset ||
+                    it <= 0 // filling beforeContentPadding area
+            } || measuredItems.all { it.isEmpty() }
         ) {
             val currentLaneIndex = currentItemOffsets.indexOfMinValue()
             val nextItemIndex =
@@ -542,7 +545,8 @@
         // only scroll backward if the first item is not on screen or fully visible
         val canScrollBackward = !(firstItemIndices[0] == 0 && firstItemOffsets[0] <= 0)
         // only scroll forward if the last item is not on screen or fully visible
-        val canScrollForward = currentItemOffsets.any { it > mainAxisAvailableSize }
+        val canScrollForward = currentItemOffsets.any { it > mainAxisAvailableSize } ||
+            currentItemIndices.all { it < itemCount - 1 }
 
         @Suppress("UNCHECKED_CAST")
         return LazyStaggeredGridMeasureResult(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
index b6fbafa..cd08ee3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
@@ -29,9 +29,11 @@
 import androidx.compose.foundation.lazy.layout.animateScrollToItem
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.saveable.listSaver
 import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.layout.Remeasurement
 import androidx.compose.ui.layout.RemeasurementModifier
 import androidx.compose.ui.unit.Constraints
@@ -133,8 +135,10 @@
     /** storage for lane assignments for each item for consistent scrolling in both directions **/
     internal val spans = LazyStaggeredGridSpans()
 
-    internal var canScrollForward = true
-    private var canScrollBackward = true
+    override var canScrollForward: Boolean by mutableStateOf(false)
+        private set
+    override var canScrollBackward: Boolean by mutableStateOf(false)
+        private set
 
     /** implementation of [LazyAnimateScrollScope] scope required for [animateScrollToItem] **/
     private val animateScrollScope = LazyStaggeredGridAnimateScrollScope(this)
@@ -147,6 +151,12 @@
         }
     }
 
+    /**
+     * Only used for testing to disable prefetching when needed to test the main logic.
+     */
+    /*@VisibleForTesting*/
+    internal var prefetchingEnabled: Boolean = true
+
     /** prefetch state used for precomputing items in the direction of scroll **/
     internal val prefetchState: LazyLayoutPrefetchState = LazyLayoutPrefetchState()
 
@@ -218,7 +228,9 @@
         if (abs(scrollToBeConsumed) > 0.5f) {
             val preScrollToBeConsumed = scrollToBeConsumed
             remeasurement?.forceRemeasure()
-            notifyPrefetch(preScrollToBeConsumed - scrollToBeConsumed)
+            if (prefetchingEnabled) {
+                notifyPrefetch(preScrollToBeConsumed - scrollToBeConsumed)
+            }
         }
 
         // here scrollToBeConsumed is already consumed during the forceRemeasure invocation
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index 919b042..2e89671 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -202,6 +202,12 @@
     override val isScrollInProgress: Boolean
         get() = lazyListState.isScrollInProgress
 
+    override val canScrollForward: Boolean
+        get() = lazyListState.canScrollForward
+
+    override val canScrollBackward: Boolean
+        get() = lazyListState.canScrollBackward
+
     private fun Int.coerceInPageRange() = coerceIn(0, pageCount - 1)
     internal fun updateOnScrollStopped() {
         settledPageState = currentPage
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt
index f5d9e74..0122d8e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldScroll.kt
@@ -17,13 +17,16 @@
 package androidx.compose.foundation.text
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableState
 import androidx.compose.foundation.gestures.rememberScrollableState
 import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.offset
 import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.listSaver
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.structuralEqualityPolicy
@@ -64,7 +67,7 @@
     // do not reverse direction only in case of RTL in horizontal orientation
     val rtl = LocalLayoutDirection.current == LayoutDirection.Rtl
     val reverseDirection = scrollerPosition.orientation == Orientation.Vertical || !rtl
-    val controller = rememberScrollableState { delta ->
+    val scrollableState = rememberScrollableState { delta ->
         val newOffset = scrollerPosition.offset + delta
         val consumedDelta = when {
             newOffset > scrollerPosition.maximum ->
@@ -75,10 +78,20 @@
         scrollerPosition.offset += consumedDelta
         consumedDelta
     }
+    // TODO: b/255557085 remove when / if rememberScrollableState exposes lambda parameters for
+    //  setting these
+    val wrappedScrollableState = remember(scrollableState, scrollerPosition) {
+        object : ScrollableState by scrollableState {
+            override val canScrollForward by derivedStateOf {
+                scrollerPosition.offset < scrollerPosition.maximum
+            }
+            override val canScrollBackward by derivedStateOf { scrollerPosition.offset > 0f }
+        }
+    }
     val scroll = Modifier.scrollable(
         orientation = scrollerPosition.orientation,
         reverseDirection = reverseDirection,
-        state = controller,
+        state = wrappedScrollableState,
         interactionSource = interactionSource,
         enabled = enabled && scrollerPosition.maximum != 0f
     )
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
index 6d663a8..48eb943 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
@@ -19,7 +19,6 @@
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
-import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -52,9 +51,6 @@
 import androidx.compose.ui.util.fastAll
 import java.awt.event.KeyEvent.VK_ENTER
 
-@Composable
-internal actual fun isComposeRootInScrollableContainer(): () -> Boolean = { false }
-
 // TODO: b/168524931 - should this depend on the input device?
 internal actual val TapIndicationDelay: Long = 0L
 
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index 968dd57..ec09563 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -369,10 +369,10 @@
 
   @androidx.compose.runtime.Stable public final class DrawerState {
     ctor public DrawerState(androidx.compose.material.DrawerValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.DrawerValue,java.lang.Boolean> confirmStateChange);
-    method @androidx.compose.material.ExperimentalMaterialApi public suspend Object? animateTo(androidx.compose.material.DrawerValue targetValue, androidx.compose.animation.core.AnimationSpec<java.lang.Float> anim, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated @androidx.compose.material.ExperimentalMaterialApi public suspend Object? animateTo(androidx.compose.material.DrawerValue targetValue, androidx.compose.animation.core.AnimationSpec<java.lang.Float> anim, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? close(kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.compose.material.DrawerValue getCurrentValue();
-    method @androidx.compose.material.ExperimentalMaterialApi public androidx.compose.runtime.State<java.lang.Float> getOffset();
+    method @androidx.compose.material.ExperimentalMaterialApi public Float? getOffset();
     method @androidx.compose.material.ExperimentalMaterialApi public androidx.compose.material.DrawerValue getTargetValue();
     method public boolean isAnimationRunning();
     method public boolean isClosed();
@@ -383,8 +383,8 @@
     property public final boolean isAnimationRunning;
     property public final boolean isClosed;
     property public final boolean isOpen;
-    property @androidx.compose.material.ExperimentalMaterialApi public final androidx.compose.runtime.State<java.lang.Float> offset;
-    property @androidx.compose.material.ExperimentalMaterialApi public final androidx.compose.material.DrawerValue targetValue;
+    property @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.material.ExperimentalMaterialApi public final Float? offset;
+    property @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.material.ExperimentalMaterialApi public final androidx.compose.material.DrawerValue targetValue;
     field public static final androidx.compose.material.DrawerState.Companion Companion;
   }
 
@@ -769,12 +769,12 @@
     method public final suspend Object? performFling(float velocity, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @androidx.compose.material.ExperimentalMaterialApi public final suspend Object? snapTo(T? targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public final T! currentValue;
-    property public final float direction;
+    property @androidx.compose.material.ExperimentalMaterialApi public final float direction;
     property public final boolean isAnimationRunning;
     property public final androidx.compose.runtime.State<java.lang.Float> offset;
     property public final androidx.compose.runtime.State<java.lang.Float> overflow;
-    property public final androidx.compose.material.SwipeProgress<T> progress;
-    property public final T! targetValue;
+    property @androidx.compose.material.ExperimentalMaterialApi public final androidx.compose.material.SwipeProgress<T> progress;
+    property @androidx.compose.material.ExperimentalMaterialApi public final T! targetValue;
     field public static final androidx.compose.material.SwipeableState.Companion Companion;
   }
 
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt
index cd1e8a0..66665e6 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt
@@ -17,7 +17,6 @@
 package androidx.compose.material
 
 import android.os.SystemClock.sleep
-import androidx.compose.animation.core.TweenSpec
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
@@ -27,6 +26,8 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.canScroll
+import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -319,12 +320,12 @@
         rule.onNodeWithTag("drawer").assertLeftPositionInRootIsEqualTo(-width)
 
         // When the drawer state is set to Opened
-        drawerState.animateTo(DrawerValue.Open, TweenSpec())
+        drawerState.open()
         // Then the drawer should be opened
         rule.onNodeWithTag("drawer").assertLeftPositionInRootIsEqualTo(0.dp)
 
         // When the drawer state is set to Closed
-        drawerState.animateTo(DrawerValue.Closed, TweenSpec())
+        drawerState.close()
         // Then the drawer should be closed
         rule.onNodeWithTag("drawer").assertLeftPositionInRootIsEqualTo(-width)
     }
@@ -1186,4 +1187,74 @@
         topNode = rule.onNodeWithTag(topTag).fetchSemanticsNode()
         assertEquals(2, topNode.children.size)
     }
+
+    @Test
+    fun modalDrawer_providesScrollableContainerInfo_enabled() {
+        var actualValue = { false }
+        rule.setMaterialContent {
+            ModalDrawer(
+                drawerContent = {},
+                content = {
+                    Box(Modifier.consumeScrollContainerInfo {
+                        actualValue = { it!!.canScroll() }
+                    })
+                }
+            )
+        }
+
+        assertThat(actualValue()).isTrue()
+    }
+
+    @Test
+    fun modalDrawer_providesScrollableContainerInfo_disabled() {
+        var actualValue = { false }
+        rule.setMaterialContent {
+            ModalDrawer(
+                drawerContent = {},
+                gesturesEnabled = false,
+                content = {
+                    Box(Modifier.consumeScrollContainerInfo {
+                        actualValue = { it!!.canScroll() }
+                    })
+                }
+            )
+        }
+
+        assertThat(actualValue()).isFalse()
+    }
+
+    @Test
+    fun bottomDrawer_providesScrollableContainerInfo_enabled() {
+        var actualValue = { false }
+        rule.setMaterialContent {
+            BottomDrawer(
+                drawerContent = {},
+                content = {
+                    Box(Modifier.consumeScrollContainerInfo {
+                        actualValue = { it!!.canScroll() }
+                    })
+                }
+            )
+        }
+
+        assertThat(actualValue()).isTrue()
+    }
+
+    @Test
+    fun bottomDrawer_providesScrollableContainerInfo_disabled() {
+        var actualValue = { false }
+        rule.setMaterialContent {
+            BottomDrawer(
+                drawerContent = {},
+                gesturesEnabled = false,
+                content = {
+                    Box(Modifier.consumeScrollContainerInfo {
+                        actualValue = { it!!.canScroll() }
+                    })
+                }
+            )
+        }
+
+        assertThat(actualValue()).isFalse()
+    }
 }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
index 6d31620..8d311a0 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
@@ -25,6 +25,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.canScroll
+import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.testTag
@@ -780,4 +782,44 @@
             assertThat(sheetState.currentValue).isEqualTo(ModalBottomSheetValue.Expanded)
         }
     }
+
+    @Test
+    fun modalBottomSheet_providesScrollableContainerInfo_hidden() {
+        var actualValue = { false }
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden),
+                content = { Box(Modifier.fillMaxSize().testTag(contentTag)) },
+                sheetContent = {
+                    Box(
+                        Modifier.fillMaxSize().consumeScrollContainerInfo {
+                            actualValue = { it!!.canScroll() }
+                        }
+                    )
+                }
+            )
+        }
+
+        assertThat(actualValue()).isFalse()
+    }
+
+    @Test
+    fun modalBottomSheet_providesScrollableContainerInfo_expanded() {
+        var actualValue = { false }
+        rule.setMaterialContent {
+            ModalBottomSheetLayout(
+                sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Expanded),
+                content = { Box(Modifier.fillMaxSize().testTag(contentTag)) },
+                sheetContent = {
+                    Box(
+                        Modifier.fillMaxSize().consumeScrollContainerInfo {
+                            actualValue = { it!!.canScroll() }
+                        }
+                    )
+                }
+            )
+        }
+
+        assertThat(actualValue()).isTrue()
+    }
 }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/swipeable/SwipeableV2GestureTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/swipeable/SwipeableV2GestureTest.kt
index a748549..303ed51 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/swipeable/SwipeableV2GestureTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/swipeable/SwipeableV2GestureTest.kt
@@ -350,9 +350,11 @@
     ) = SwipeableV2State(
         initialValue = initialState,
         positionalThreshold = positionalThreshold,
-        velocityThreshold = velocityThreshold,
-        density = density
-    ).apply { if (anchors != null) updateAnchors(anchors) }
+        velocityThreshold = velocityThreshold
+    ).apply {
+        if (anchors != null) updateAnchors(anchors)
+        this.density = density
+    }
 
     private fun TouchInjectionScope.endEdge(orientation: Orientation) =
         if (orientation == Orientation.Horizontal) right else bottom
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
index 4099380..12f80a5 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
@@ -32,7 +32,6 @@
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -44,8 +43,10 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.isSpecified
+import androidx.compose.ui.input.ScrollContainerInfo
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.provideScrollContainerInfo
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
@@ -58,10 +59,10 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.launch
 import kotlin.math.max
 import kotlin.math.roundToInt
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.launch
 
 /**
  * Possible values of [DrawerState].
@@ -113,10 +114,11 @@
     confirmStateChange: (DrawerValue) -> Boolean = { true }
 ) {
 
-    internal val swipeableState = SwipeableState(
+    internal val swipeableState = SwipeableV2State(
         initialValue = initialValue,
         animationSpec = AnimationSpec,
-        confirmStateChange = confirmStateChange
+        confirmValueChange = confirmStateChange,
+        velocityThreshold = DrawerVelocityThreshold
     )
 
     /**
@@ -158,7 +160,7 @@
      *
      * @return the reason the open animation ended
      */
-    suspend fun open() = animateTo(DrawerValue.Open, AnimationSpec)
+    suspend fun open() = swipeableState.animateTo(DrawerValue.Open)
 
     /**
      * Close the drawer with animation and suspend until it if fully closed or animation has been
@@ -167,17 +169,25 @@
      *
      * @return the reason the close animation ended
      */
-    suspend fun close() = animateTo(DrawerValue.Closed, AnimationSpec)
+    suspend fun close() = swipeableState.animateTo(DrawerValue.Closed)
 
     /**
      * Set the state of the drawer with specific animation
      *
      * @param targetValue The new value to animate to.
-     * @param anim The animation that will be used to animate to the new value.
+     * @param anim Set the state of the drawer with specific animation
      */
     @ExperimentalMaterialApi
-    suspend fun animateTo(targetValue: DrawerValue, anim: AnimationSpec<Float>) {
-        swipeableState.animateTo(targetValue, anim)
+    @Deprecated(
+        message = "This method has been replaced by the open and close methods. The animation " +
+            "spec is now an implementation detail of ModalDrawer.",
+        level = DeprecationLevel.ERROR
+    )
+    suspend fun animateTo(
+        targetValue: DrawerValue,
+        @Suppress("UNUSED_PARAMETER") anim: AnimationSpec<Float>
+    ) {
+        swipeableState.animateTo(targetValue)
     }
 
     /**
@@ -204,14 +214,19 @@
         get() = swipeableState.targetValue
 
     /**
-     * The current position (in pixels) of the drawer sheet.
+     * The current position (in pixels) of the drawer sheet, or null before the offset is
+     * initialized.
+     * @see [SwipeableV2State.offset] for more information.
      */
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:Suppress("AutoBoxing")
     @ExperimentalMaterialApi
     @get:ExperimentalMaterialApi
-    val offset: State<Float>
+    val offset: Float?
         get() = swipeableState.offset
 
+    internal fun requireOffset(): Float = swipeableState.requireOffset()
+
     companion object {
         /**
          * The default [Saver] implementation for [DrawerState].
@@ -384,6 +399,14 @@
     content: @Composable () -> Unit
 ) {
     val scope = rememberCoroutineScope()
+
+    val containerInfo = remember(gesturesEnabled) {
+        object : ScrollContainerInfo {
+            override fun canScrollHorizontally() = gesturesEnabled
+
+            override fun canScrollVertically() = false
+        }
+    }
     BoxWithConstraints(modifier.fillMaxSize()) {
         val modalDrawerConstraints = constraints
         // TODO : think about Infinite max bounds case
@@ -394,19 +417,25 @@
         val minValue = -modalDrawerConstraints.maxWidth.toFloat()
         val maxValue = 0f
 
-        val anchors = mapOf(minValue to DrawerValue.Closed, maxValue to DrawerValue.Open)
         val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
         Box(
-            Modifier.swipeable(
-                state = drawerState.swipeableState,
-                anchors = anchors,
-                thresholds = { _, _ -> FractionalThreshold(0.5f) },
-                orientation = Orientation.Horizontal,
-                enabled = gesturesEnabled,
-                reverseDirection = isRtl,
-                velocityThreshold = DrawerVelocityThreshold,
-                resistance = null
-            )
+            Modifier
+                .swipeableV2(
+                    state = drawerState.swipeableState,
+                    orientation = Orientation.Horizontal,
+                    enabled = gesturesEnabled,
+                    reverseDirection = isRtl
+                )
+                .swipeAnchors(
+                    drawerState.swipeableState,
+                    possibleValues = setOf(DrawerValue.Closed, DrawerValue.Open)
+                ) { value, _ ->
+                    when (value) {
+                        DrawerValue.Closed -> minValue
+                        DrawerValue.Open -> maxValue
+                    }
+                }
+                .provideScrollContainerInfo(containerInfo)
         ) {
             Box {
                 content()
@@ -416,13 +445,13 @@
                 onClose = {
                     if (
                         gesturesEnabled &&
-                        drawerState.swipeableState.confirmStateChange(DrawerValue.Closed)
+                        drawerState.swipeableState.confirmValueChange(DrawerValue.Closed)
                     ) {
                         scope.launch { drawerState.close() }
                     }
                 },
                 fraction = {
-                    calculateFraction(minValue, maxValue, drawerState.offset.value)
+                    calculateFraction(minValue, maxValue, drawerState.requireOffset())
                 },
                 color = scrimColor
             )
@@ -437,7 +466,13 @@
                             maxHeight = modalDrawerConstraints.maxHeight.toDp()
                         )
                 }
-                    .offset { IntOffset(drawerState.offset.value.roundToInt(), 0) }
+                    .offset {
+                        IntOffset(
+                            drawerState
+                                .requireOffset()
+                                .roundToInt(), 0
+                        )
+                    }
                     .padding(end = EndDrawerPadding)
                     .semantics {
                         paneTitle = navigationMenu
@@ -445,7 +480,7 @@
                             dismiss {
                                 if (
                                     drawerState.swipeableState
-                                        .confirmStateChange(DrawerValue.Closed)
+                                        .confirmValueChange(DrawerValue.Closed)
                                 ) {
                                     scope.launch { drawerState.close() }
                                 }; true
@@ -541,6 +576,15 @@
         } else {
             Modifier
         }
+
+        val containerInfo = remember(gesturesEnabled) {
+            object : ScrollContainerInfo {
+                override fun canScrollHorizontally() = gesturesEnabled
+
+                override fun canScrollVertically() = false
+            }
+        }
+
         val swipeable = Modifier
             .then(nestedScroll)
             .swipeable(
@@ -550,6 +594,7 @@
                 enabled = gesturesEnabled,
                 resistance = null
             )
+            .provideScrollContainerInfo(containerInfo)
 
         Box(swipeable) {
             content()
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index 3ba169b..919da41 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -44,8 +44,10 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.isSpecified
+import androidx.compose.ui.input.ScrollContainerInfo
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.provideScrollContainerInfo
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.semantics.collapse
 import androidx.compose.ui.semantics.contentDescription
@@ -338,6 +340,15 @@
                 visible = sheetState.targetValue != Hidden
             )
         }
+
+        val containerInfo = remember(sheetState) {
+            object : ScrollContainerInfo {
+                override fun canScrollHorizontally() = false
+
+                override fun canScrollVertically() = sheetState.currentValue != Hidden
+            }
+        }
+
         Surface(
             Modifier
                 .fillMaxWidth()
@@ -353,6 +364,7 @@
                     IntOffset(0, y)
                 }
                 .bottomSheetSwipeable(sheetState, fullHeight, sheetHeightState)
+                .provideScrollContainerInfo(containerInfo)
                 .onGloballyPositioned {
                     sheetHeightState.value = it.size.height.toFloat()
                 }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeableV2.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeableV2.kt
index 70c2766..9ee4ffb 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeableV2.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeableV2.kt
@@ -33,8 +33,15 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.layout.LayoutModifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.OnRemeasuredModifier
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.InspectorValueInfo
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntSize
@@ -98,22 +105,32 @@
     possibleValues: Set<T>,
     anchorsChanged: ((oldAnchors: Map<T, Float>, newAnchors: Map<T, Float>) -> Unit)? = null,
     calculateAnchor: (value: T, layoutSize: IntSize) -> Float?,
-) = onSizeChanged { layoutSize ->
-    val previousAnchors = state.anchors
-    val newAnchors = mutableMapOf<T, Float>()
-    possibleValues.forEach {
-        val anchorValue = calculateAnchor(it, layoutSize)
-        if (anchorValue != null) {
-            newAnchors[it] = anchorValue
+) = this.then(SwipeAnchorsModifier(
+    onDensityChanged = { state.density = it },
+    onSizeChanged = { layoutSize ->
+        val previousAnchors = state.anchors
+        val newAnchors = mutableMapOf<T, Float>()
+        possibleValues.forEach {
+            val anchorValue = calculateAnchor(it, layoutSize)
+            if (anchorValue != null) {
+                newAnchors[it] = anchorValue
+            }
         }
+        if (previousAnchors != newAnchors) {
+            state.updateAnchors(newAnchors)
+            if (previousAnchors.isNotEmpty()) {
+                anchorsChanged?.invoke(previousAnchors, newAnchors)
+            }
+        }
+    },
+    inspectorInfo = debugInspectorInfo {
+        name = "swipeAnchors"
+        properties["state"] = state
+        properties["possibleValues"] = possibleValues
+        properties["anchorsChanged"] = anchorsChanged
+        properties["calculateAnchor"] = calculateAnchor
     }
-    if (previousAnchors == newAnchors) return@onSizeChanged
-    state.updateAnchors(newAnchors)
-
-    if (previousAnchors.isNotEmpty()) {
-        anchorsChanged?.invoke(previousAnchors, newAnchors)
-    }
-}
+))
 
 /**
  * State of the [swipeableV2] modifier.
@@ -123,7 +140,6 @@
  * [SwipeableV2State] use [rememberSwipeableV2State].
  *
  * @param initialValue The initial value of the state.
- * @param density The density used to convert thresholds from px to dp.
  * @param animationSpec The default animation that will be used to animate to a new state.
  * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
  * @param positionalThreshold The positional threshold to be used when calculating the target state
@@ -139,7 +155,6 @@
 @ExperimentalMaterialApi
 internal class SwipeableV2State<T>(
     initialValue: T,
-    internal val density: Density,
     internal val animationSpec: AnimationSpec<Float> = SwipeableV2Defaults.AnimationSpec,
     internal val confirmValueChange: (newValue: T) -> Boolean = { true },
     internal val positionalThreshold: Density.(totalDistance: Float) -> Float =
@@ -178,6 +193,7 @@
      *
      * To guarantee stricter semantics, consider using [requireOffset].
      */
+    @get:Suppress("AutoBoxing")
     val offset: Float? by derivedStateOf {
         dragPosition?.coerceIn(minBound, maxBound)
     }
@@ -228,14 +244,14 @@
     private val minBound by derivedStateOf { anchors.minOrNull() ?: Float.NEGATIVE_INFINITY }
     private val maxBound by derivedStateOf { anchors.maxOrNull() ?: Float.POSITIVE_INFINITY }
 
-    private val velocityThresholdPx = with(density) { velocityThreshold.toPx() }
-
     internal val draggableState = DraggableState {
         dragPosition = (dragPosition ?: 0f) + it
     }
 
     internal var anchors by mutableStateOf(emptyMap<T, Float>())
 
+    internal var density: Density? = null
+
     internal fun updateAnchors(newAnchors: Map<T, Float>) {
         val previousAnchorsEmpty = anchors.isEmpty()
         anchors = newAnchors
@@ -348,6 +364,8 @@
     ): T {
         val currentAnchors = anchors
         val currentAnchor = currentAnchors.requireAnchor(currentValue)
+        val currentDensity = requireDensity()
+        val velocityThresholdPx = with(currentDensity) { velocityThreshold.toPx() }
         return if (currentAnchor <= offset) {
             // Swiping from lower to upper (positive).
             if (velocity >= velocityThresholdPx) {
@@ -355,7 +373,7 @@
             } else {
                 val upper = currentAnchors.closestAnchor(offset, true)
                 val distance = abs(currentAnchors.getValue(upper) - currentAnchor)
-                val relativeThreshold = abs(positionalThreshold(density, distance))
+                val relativeThreshold = abs(positionalThreshold(currentDensity, distance))
                 val absoluteThreshold = abs(currentAnchor + relativeThreshold)
                 if (offset < absoluteThreshold) currentValue else upper
             }
@@ -366,7 +384,7 @@
             } else {
                 val lower = currentAnchors.closestAnchor(offset, false)
                 val distance = abs(currentAnchor - currentAnchors.getValue(lower))
-                val relativeThreshold = abs(positionalThreshold(density, distance))
+                val relativeThreshold = abs(positionalThreshold(currentDensity, distance))
                 val absoluteThreshold = abs(currentAnchor - relativeThreshold)
                 if (offset < 0) {
                     // For negative offsets, larger absolute thresholds are closer to lower anchors
@@ -379,6 +397,11 @@
         }
     }
 
+    private fun requireDensity() = requireNotNull(density) {
+        "SwipeableState did not have a density attached. Are you using Modifier.swipeable with " +
+            "this=$this SwipeableState?"
+    }
+
     companion object {
         /**
          * The default [Saver] implementation for [SwipeableV2State].
@@ -388,8 +411,7 @@
             animationSpec: AnimationSpec<Float>,
             confirmValueChange: (T) -> Boolean,
             positionalThreshold: Density.(distance: Float) -> Float,
-            velocityThreshold: Dp,
-            density: Density
+            velocityThreshold: Dp
         ) = Saver<SwipeableV2State<T>, T>(
             save = { it.currentValue },
             restore = {
@@ -398,8 +420,7 @@
                     animationSpec = animationSpec,
                     confirmValueChange = confirmValueChange,
                     positionalThreshold = positionalThreshold,
-                    velocityThreshold = velocityThreshold,
-                    density = density
+                    velocityThreshold = velocityThreshold
                 )
             }
         )
@@ -420,15 +441,13 @@
     animationSpec: AnimationSpec<Float> = SwipeableV2Defaults.AnimationSpec,
     confirmValueChange: (newValue: T) -> Boolean = { true }
 ): SwipeableV2State<T> {
-    val density = LocalDensity.current
     return rememberSaveable(
-        initialValue, animationSpec, confirmValueChange, density,
+        initialValue, animationSpec, confirmValueChange,
         saver = SwipeableV2State.Saver(
             animationSpec = animationSpec,
             confirmValueChange = confirmValueChange,
             positionalThreshold = SwipeableV2Defaults.PositionalThreshold,
-            velocityThreshold = SwipeableV2Defaults.VelocityThreshold,
-            density = density
+            velocityThreshold = SwipeableV2Defaults.VelocityThreshold
         ),
     ) {
         SwipeableV2State(
@@ -436,8 +455,7 @@
             animationSpec = animationSpec,
             confirmValueChange = confirmValueChange,
             positionalThreshold = SwipeableV2Defaults.PositionalThreshold,
-            velocityThreshold = SwipeableV2Defaults.VelocityThreshold,
-            density = density
+            velocityThreshold = SwipeableV2Defaults.VelocityThreshold
         )
     }
 }
@@ -491,6 +509,37 @@
         fixedPositionalThreshold(56.dp)
 }
 
+@Stable
+private class SwipeAnchorsModifier(
+    private val onDensityChanged: (density: Density) -> Unit,
+    private val onSizeChanged: (layoutSize: IntSize) -> Unit,
+    inspectorInfo: InspectorInfo.() -> Unit,
+) : LayoutModifier, OnRemeasuredModifier, InspectorValueInfo(inspectorInfo) {
+
+    private var lastDensity: Float = -1f
+    private var lastFontScale: Float = -1f
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        if (density != lastDensity || fontScale != lastFontScale) {
+            onDensityChanged(Density(density, fontScale))
+            lastDensity = density
+            lastFontScale = fontScale
+        }
+        val placeable = measurable.measure(constraints)
+        return layout(placeable.width, placeable.height) { placeable.place(0, 0) }
+    }
+
+    override fun onRemeasured(size: IntSize) {
+        onSizeChanged(size)
+    }
+
+    override fun toString() = "SwipeAnchorsModifierImpl(updateDensity=$onDensityChanged, " +
+        "onSizeChanged=$onSizeChanged)"
+}
+
 private fun <T> Map<T, Float>.closestAnchor(
     offset: Float = 0f,
     searchUpwards: Boolean = false
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index d92a1b2..8230350 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -326,8 +326,8 @@
     property public final boolean isAnimationRunning;
     property public final boolean isClosed;
     property public final boolean isOpen;
-    property @androidx.compose.material3.ExperimentalMaterial3Api public final androidx.compose.runtime.State<java.lang.Float> offset;
-    property @androidx.compose.material3.ExperimentalMaterial3Api public final androidx.compose.material3.DrawerValue targetValue;
+    property @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.material3.ExperimentalMaterial3Api public final androidx.compose.runtime.State<java.lang.Float> offset;
+    property @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.material3.ExperimentalMaterial3Api public final androidx.compose.material3.DrawerValue targetValue;
     field public static final androidx.compose.material3.DrawerState.Companion Companion;
   }
 
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
index 2edb2e5..6deaf4d 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
@@ -26,6 +26,8 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.canScroll
+import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -558,6 +560,44 @@
                 .onParent()
                 .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Dismiss))
         }
+
+    @Test
+    fun dismissibleNavigationDrawer_providesScrollableContainerInfo_enabled() {
+        var actualValue = { false }
+        rule.setMaterialContent(lightColorScheme()) {
+
+            DismissibleNavigationDrawer(
+                gesturesEnabled = true,
+                drawerContent = {},
+                content = {
+                    Box(Modifier.consumeScrollContainerInfo {
+                        actualValue = { it!!.canScroll() }
+                    })
+                }
+            )
+        }
+
+        assertThat(actualValue()).isTrue()
+    }
+
+    @Test
+    fun dismissibleNavigationDrawer_providesScrollableContainerInfo_disabled() {
+        var actualValue = { false }
+        rule.setMaterialContent(lightColorScheme()) {
+
+            DismissibleNavigationDrawer(
+                gesturesEnabled = false,
+                drawerContent = {},
+                content = {
+                    Box(Modifier.consumeScrollContainerInfo {
+                        actualValue = { it!!.canScroll() }
+                    })
+                }
+            )
+        }
+
+        assertThat(actualValue()).isFalse()
+    }
 }
 
 private val DrawerTestTag = "drawer"
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
index 409120f..f18376cc 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
@@ -26,6 +26,8 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.canScroll
+import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -656,6 +658,45 @@
         topNode = rule.onNodeWithTag(topTag).fetchSemanticsNode()
         assertEquals(2, topNode.children.size)
     }
+
+    @Test
+    fun navigationDrawer_providesScrollableContainerInfo_enabled() {
+        var actualValue = { false }
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawer(
+                drawerContent = { ModalDrawerSheet { } },
+                content = {
+                    Box(
+                        Modifier.consumeScrollContainerInfo {
+                            actualValue = { it!!.canScroll() }
+                        }
+                    )
+                }
+            )
+        }
+
+        assertThat(actualValue()).isTrue()
+    }
+
+    @Test
+    fun navigationDrawer_providesScrollableContainerInfo_disabled() {
+        var actualValue = { false }
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawer(
+                gesturesEnabled = false,
+                drawerContent = { ModalDrawerSheet { } },
+                content = {
+                    Box(
+                        Modifier.consumeScrollContainerInfo {
+                            actualValue = { it!!.canScroll() }
+                        }
+                    )
+                }
+            )
+        }
+
+        assertThat(actualValue()).isFalse()
+    }
 }
 
 private val DrawerTestTag = "drawer"
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
index d3cc997..f003baf 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="dialog" msgid="4057925834421392736">"Dialogue"</string>
+    <string name="dialog" msgid="4057925834421392736">"Dialog"</string>
     <string name="expanded" msgid="5974471714631304645">"Expanded"</string>
     <string name="collapsed" msgid="5389587048670450460">"Collapsed"</string>
 </resources>
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
index ae8de70..38b30e4 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
@@ -57,7 +57,9 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.input.ScrollContainerInfo
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.provideScrollContainerInfo
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
@@ -268,6 +270,15 @@
 
     val anchors = mapOf(minValue to DrawerValue.Closed, maxValue to DrawerValue.Open)
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+
+    val containerInfo = remember(gesturesEnabled) {
+        object : ScrollContainerInfo {
+            override fun canScrollHorizontally() = gesturesEnabled
+
+            override fun canScrollVertically() = false
+        }
+    }
+
     Box(
         modifier
             .fillMaxSize()
@@ -281,6 +292,7 @@
                 velocityThreshold = DrawerVelocityThreshold,
                 resistance = null
             )
+            .provideScrollContainerInfo(containerInfo)
     ) {
         Box {
             content()
@@ -361,6 +373,14 @@
 
     val anchors = mapOf(minValue to DrawerValue.Closed, maxValue to DrawerValue.Open)
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+    val containerInfo = remember(gesturesEnabled) {
+        object : ScrollContainerInfo {
+            override fun canScrollHorizontally() = gesturesEnabled
+
+            override fun canScrollVertically() = false
+        }
+    }
+
     Box(
         modifier.swipeable(
             state = drawerState.swipeableState,
@@ -371,7 +391,7 @@
             reverseDirection = isRtl,
             velocityThreshold = DrawerVelocityThreshold,
             resistance = null
-        )
+        ).provideScrollContainerInfo(containerInfo)
     ) {
         Layout(content = {
             Box(Modifier.semantics {
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt
index cd738fe..1ed4cd9 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt
@@ -107,7 +107,7 @@
             if (name != "content" && parameterInfo.functionType.parameters.isEmpty()) {
                 context.report(
                     ComposableLambdaParameterNaming,
-                    node,
+                    uElement,
                     context.getNameLocation(uElement),
                     "Composable lambda parameter should be named `content`",
                     LintFix.create()
@@ -123,7 +123,7 @@
             if (parameter !== node.uastParameters.last()) {
                 context.report(
                     ComposableLambdaParameterPosition,
-                    node,
+                    uElement,
                     context.getNameLocation(uElement),
                     "Composable lambda parameter should be the last parameter so it can be used " +
                         "as a trailing lambda"
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt
index 4ace0ae..463830d 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt
@@ -59,7 +59,6 @@
             Stubs.Composable
         )
             .skipTestModes(TestMode.TYPE_ALIAS)
-            .skipTestModes(TestMode.SUPPRESSIBLE) // b/257293458
             .run()
             .expect(
                 """
@@ -97,7 +96,6 @@
             Stubs.Composable
         )
             .skipTestModes(TestMode.TYPE_ALIAS)
-            .skipTestModes(TestMode.SUPPRESSIBLE) // b/257293458
             .run()
             .expect(
                 """
@@ -127,7 +125,6 @@
             Stubs.Composable
         )
             .skipTestModes(TestMode.TYPE_ALIAS)
-            .skipTestModes(TestMode.SUPPRESSIBLE) // b/257293458
             .run()
             .expect(
                 """
@@ -169,7 +166,6 @@
             ),
             Stubs.Composable
         )
-            .skipTestModes(TestMode.SUPPRESSIBLE) // b/257293458
             .run()
             .expect(
                 """
@@ -235,7 +231,6 @@
             Stubs.Composable
         )
             .skipTestModes(TestMode.TYPE_ALIAS)
-            .skipTestModes(TestMode.SUPPRESSIBLE) // b/257293458
             .run()
             .expect(
                 """
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index 56de5b4..ecc892b 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -150,7 +150,6 @@
     method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
     method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
     property public abstract androidx.compose.runtime.Applier<?> applier;
-    property @org.jetbrains.annotations.TestOnly public abstract kotlin.coroutines.CoroutineContext applyCoroutineContext;
     property @org.jetbrains.annotations.TestOnly public abstract androidx.compose.runtime.ControlledComposition composition;
     property public abstract androidx.compose.runtime.tooling.CompositionData compositionData;
     property public abstract int compoundKeyHash;
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 82341f0..55e2337 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -163,7 +163,7 @@
     method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
     method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
     property public abstract androidx.compose.runtime.Applier<?> applier;
-    property @org.jetbrains.annotations.TestOnly public abstract kotlin.coroutines.CoroutineContext applyCoroutineContext;
+    property @androidx.compose.runtime.InternalComposeApi @org.jetbrains.annotations.TestOnly public abstract kotlin.coroutines.CoroutineContext applyCoroutineContext;
     property @org.jetbrains.annotations.TestOnly public abstract androidx.compose.runtime.ControlledComposition composition;
     property public abstract androidx.compose.runtime.tooling.CompositionData compositionData;
     property public abstract int compoundKeyHash;
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 900b19e..0f35507 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -152,7 +152,6 @@
     method @androidx.compose.runtime.ComposeCompilerApi public void updateRememberedValue(Object? value);
     method @androidx.compose.runtime.ComposeCompilerApi public void useNode();
     property public abstract androidx.compose.runtime.Applier<?> applier;
-    property @org.jetbrains.annotations.TestOnly public abstract kotlin.coroutines.CoroutineContext applyCoroutineContext;
     property @org.jetbrains.annotations.TestOnly public abstract androidx.compose.runtime.ControlledComposition composition;
     property public abstract androidx.compose.runtime.tooling.CompositionData compositionData;
     property public abstract int compoundKeyHash;
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index e637a6a..f0e18ad 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -412,8 +412,14 @@
     private fun recordComposerModificationsLocked() {
         val changes = snapshotInvalidations
         if (changes.isNotEmpty()) {
-            knownCompositions.fastForEach { composition ->
-                composition.recordModificationsOf(changes)
+            run {
+                knownCompositions.fastForEach { composition ->
+                    composition.recordModificationsOf(changes)
+
+                    // Avoid using knownCompositions if recording modification detected a
+                    // shutdown of the recomposer.
+                    if (_state.value <= State.ShuttingDown) return@run
+                }
             }
             snapshotInvalidations = mutableSetOf()
             if (deriveStateLocked() != null) {
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
index 5c32e29..285838a 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -54,7 +54,6 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runTest
-import org.junit.Ignore
 
 @Composable
 fun Container(content: @Composable () -> Unit) = content()
@@ -3235,7 +3234,6 @@
     }
 
     @Test
-    @Ignore("b/255722247")
     fun testNonLocalReturn_CM1_RetFunc_FalseTrue() = compositionTest {
         var condition by mutableStateOf(false)
 
@@ -3255,7 +3253,6 @@
     }
 
     @Test
-    @Ignore("b/255722247")
     fun testNonLocalReturn_CM1_RetFunc_TrueFalse() = compositionTest {
         var condition by mutableStateOf(true)
 
@@ -3275,7 +3272,6 @@
     }
 
     @Test
-    @Ignore("b/255722247")
     fun test_CM1_CCM1_RetFun_FalseTrue() = compositionTest {
         var condition by mutableStateOf(false)
 
@@ -3295,7 +3291,6 @@
     }
 
     @Test
-    @Ignore("b/255722247")
     fun test_CM1_CCM1_RetFun_TrueFalse() = compositionTest {
         var condition by mutableStateOf(true)
 
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
index 65078df..0ca1631 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
@@ -37,6 +37,8 @@
 import kotlin.test.assertEquals
 import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.test.StandardTestDispatcher
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class RecomposerTests {
@@ -309,6 +311,70 @@
     fun constructRecomposerWithCancelledJob() {
         Recomposer(Job().apply { cancel() })
     }
+
+    @Test // regression test for b/243862703
+    fun cancelWithPendingInvalidations() {
+        val dispatcher = StandardTestDispatcher()
+        runTest(dispatcher) {
+            val testClock = TestMonotonicFrameClock(this)
+            withContext(testClock) {
+
+                val recomposer = Recomposer(coroutineContext)
+                var launched = false
+                val runner = launch {
+                    launched = true
+                    recomposer.runRecomposeAndApplyChanges()
+                }
+                val compositionOne = Composition(UnitApplier(), recomposer)
+                val compositionTwo = Composition(UnitApplier(), recomposer)
+                var state by mutableStateOf(0)
+                var lastCompositionOneState = -1
+                var lastCompositionTwoState = -1
+                compositionOne.setContent {
+                    lastCompositionOneState = state
+                    LaunchedEffect(Unit) {
+                        delay(1_000)
+                    }
+                }
+                compositionTwo.setContent {
+                    lastCompositionTwoState = state
+                    LaunchedEffect(Unit) {
+                        delay(1_000)
+                    }
+                }
+
+                assertEquals(0, lastCompositionOneState, "initial composition")
+                assertEquals(0, lastCompositionTwoState, "initial composition")
+
+                dispatcher.scheduler.runCurrent()
+
+                assertNotNull(
+                    withTimeoutOrNull(3_000) { recomposer.awaitIdle() },
+                    "timed out waiting for recomposer idle for recomposition"
+                )
+
+                dispatcher.scheduler.runCurrent()
+
+                assertTrue(launched, "Recomposer was never started")
+
+                Snapshot.withMutableSnapshot {
+                    state = 1
+                }
+
+                recomposer.cancel()
+
+                assertNotNull(
+                    withTimeoutOrNull(3_000) { recomposer.awaitIdle() },
+                    "timed out waiting for recomposer idle for recomposition"
+                )
+
+                assertNotNull(
+                    withTimeoutOrNull(3_000) { runner.join() },
+                    "timed out waiting for recomposer runner job"
+                )
+            }
+        }
+    }
 }
 
 class UnitApplier : Applier<Unit> {
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/reflect/ComposableMethod.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/reflect/ComposableMethod.kt
index b8f53ff..51c93dc 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/reflect/ComposableMethod.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/reflect/ComposableMethod.kt
@@ -155,7 +155,7 @@
                 realParamsCount -> composer
                 // since this is the root we don't need to be anything unique. 0 should suffice.
                 // changed parameters should be 0 to indicate "uncertain"
-                changedStartIndex -> 1
+                changedStartIndex -> 0
                 in changedStartIndex + 1 until defaultStartIndex -> 0
                 // Default values mask, all parameters set to use defaults
                 in defaultStartIndex until totalParams -> defaultsMasks[idx - defaultStartIndex]
diff --git a/compose/ui/ui-geometry/api/current.txt b/compose/ui/ui-geometry/api/current.txt
index 583e373..0be492c 100644
--- a/compose/ui/ui-geometry/api/current.txt
+++ b/compose/ui/ui-geometry/api/current.txt
@@ -12,8 +12,8 @@
     method @androidx.compose.runtime.Stable public operator long plus(long other);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.geometry.CornerRadius.Companion Companion;
   }
 
@@ -76,8 +76,8 @@
     method @androidx.compose.runtime.Stable public operator long rem(float operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.geometry.Offset.Companion Companion;
   }
 
@@ -141,20 +141,20 @@
     property public final long center;
     property public final long centerLeft;
     property public final long centerRight;
-    property public final float height;
-    property public final boolean isEmpty;
-    property public final boolean isFinite;
-    property public final boolean isInfinite;
+    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final boolean isEmpty;
+    property @androidx.compose.runtime.Stable public final boolean isFinite;
+    property @androidx.compose.runtime.Stable public final boolean isInfinite;
     property public final float left;
     property public final float maxDimension;
     property public final float minDimension;
     property public final float right;
-    property public final long size;
+    property @androidx.compose.runtime.Stable public final long size;
     property public final float top;
     property public final long topCenter;
     property public final long topLeft;
     property public final long topRight;
-    property public final float width;
+    property @androidx.compose.runtime.Stable public final float width;
     field public static final androidx.compose.ui.geometry.Rect.Companion Companion;
   }
 
@@ -244,10 +244,10 @@
     method public float getWidth();
     method @androidx.compose.runtime.Stable public boolean isEmpty();
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    property public final float height;
-    property public final float maxDimension;
-    property public final float minDimension;
-    property public final float width;
+    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final float maxDimension;
+    property @androidx.compose.runtime.Stable public final float minDimension;
+    property @androidx.compose.runtime.Stable public final float width;
     field public static final androidx.compose.ui.geometry.Size.Companion Companion;
   }
 
diff --git a/compose/ui/ui-geometry/api/public_plus_experimental_current.txt b/compose/ui/ui-geometry/api/public_plus_experimental_current.txt
index 583e373..0be492c 100644
--- a/compose/ui/ui-geometry/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-geometry/api/public_plus_experimental_current.txt
@@ -12,8 +12,8 @@
     method @androidx.compose.runtime.Stable public operator long plus(long other);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.geometry.CornerRadius.Companion Companion;
   }
 
@@ -76,8 +76,8 @@
     method @androidx.compose.runtime.Stable public operator long rem(float operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.geometry.Offset.Companion Companion;
   }
 
@@ -141,20 +141,20 @@
     property public final long center;
     property public final long centerLeft;
     property public final long centerRight;
-    property public final float height;
-    property public final boolean isEmpty;
-    property public final boolean isFinite;
-    property public final boolean isInfinite;
+    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final boolean isEmpty;
+    property @androidx.compose.runtime.Stable public final boolean isFinite;
+    property @androidx.compose.runtime.Stable public final boolean isInfinite;
     property public final float left;
     property public final float maxDimension;
     property public final float minDimension;
     property public final float right;
-    property public final long size;
+    property @androidx.compose.runtime.Stable public final long size;
     property public final float top;
     property public final long topCenter;
     property public final long topLeft;
     property public final long topRight;
-    property public final float width;
+    property @androidx.compose.runtime.Stable public final float width;
     field public static final androidx.compose.ui.geometry.Rect.Companion Companion;
   }
 
@@ -244,10 +244,10 @@
     method public float getWidth();
     method @androidx.compose.runtime.Stable public boolean isEmpty();
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    property public final float height;
-    property public final float maxDimension;
-    property public final float minDimension;
-    property public final float width;
+    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final float maxDimension;
+    property @androidx.compose.runtime.Stable public final float minDimension;
+    property @androidx.compose.runtime.Stable public final float width;
     field public static final androidx.compose.ui.geometry.Size.Companion Companion;
   }
 
diff --git a/compose/ui/ui-geometry/api/restricted_current.txt b/compose/ui/ui-geometry/api/restricted_current.txt
index 583e373..0be492c 100644
--- a/compose/ui/ui-geometry/api/restricted_current.txt
+++ b/compose/ui/ui-geometry/api/restricted_current.txt
@@ -12,8 +12,8 @@
     method @androidx.compose.runtime.Stable public operator long plus(long other);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.geometry.CornerRadius.Companion Companion;
   }
 
@@ -76,8 +76,8 @@
     method @androidx.compose.runtime.Stable public operator long rem(float operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.geometry.Offset.Companion Companion;
   }
 
@@ -141,20 +141,20 @@
     property public final long center;
     property public final long centerLeft;
     property public final long centerRight;
-    property public final float height;
-    property public final boolean isEmpty;
-    property public final boolean isFinite;
-    property public final boolean isInfinite;
+    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final boolean isEmpty;
+    property @androidx.compose.runtime.Stable public final boolean isFinite;
+    property @androidx.compose.runtime.Stable public final boolean isInfinite;
     property public final float left;
     property public final float maxDimension;
     property public final float minDimension;
     property public final float right;
-    property public final long size;
+    property @androidx.compose.runtime.Stable public final long size;
     property public final float top;
     property public final long topCenter;
     property public final long topLeft;
     property public final long topRight;
-    property public final float width;
+    property @androidx.compose.runtime.Stable public final float width;
     field public static final androidx.compose.ui.geometry.Rect.Companion Companion;
   }
 
@@ -244,10 +244,10 @@
     method public float getWidth();
     method @androidx.compose.runtime.Stable public boolean isEmpty();
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    property public final float height;
-    property public final float maxDimension;
-    property public final float minDimension;
-    property public final float width;
+    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final float maxDimension;
+    property @androidx.compose.runtime.Stable public final float minDimension;
+    property @androidx.compose.runtime.Stable public final float width;
     field public static final androidx.compose.ui.geometry.Size.Companion Companion;
   }
 
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index ab55dfe..58c76a6 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -314,11 +314,11 @@
     method public float getGreen();
     method public float getRed();
     method public long getValue();
-    property public final float alpha;
-    property public final float blue;
-    property public final androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace;
-    property public final float green;
-    property public final float red;
+    property @androidx.compose.runtime.Stable public final float alpha;
+    property @androidx.compose.runtime.Stable public final float blue;
+    property @androidx.compose.runtime.Stable public final androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace;
+    property @androidx.compose.runtime.Stable public final float green;
+    property @androidx.compose.runtime.Stable public final float red;
     property public final long value;
     field public static final androidx.compose.ui.graphics.Color.Companion Companion;
   }
@@ -900,7 +900,7 @@
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ColorModel {
     method public int getComponentCount();
-    property public final int componentCount;
+    property @androidx.compose.runtime.Stable public final int componentCount;
     field public static final androidx.compose.ui.graphics.colorspace.ColorModel.Companion Companion;
   }
 
diff --git a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
index 9b18442..fc54376 100644
--- a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -314,11 +314,11 @@
     method public float getGreen();
     method public float getRed();
     method public long getValue();
-    property public final float alpha;
-    property public final float blue;
-    property public final androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace;
-    property public final float green;
-    property public final float red;
+    property @androidx.compose.runtime.Stable public final float alpha;
+    property @androidx.compose.runtime.Stable public final float blue;
+    property @androidx.compose.runtime.Stable public final androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace;
+    property @androidx.compose.runtime.Stable public final float green;
+    property @androidx.compose.runtime.Stable public final float red;
     property public final long value;
     field public static final androidx.compose.ui.graphics.Color.Companion Companion;
   }
@@ -903,7 +903,7 @@
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ColorModel {
     method public int getComponentCount();
-    property public final int componentCount;
+    property @androidx.compose.runtime.Stable public final int componentCount;
     field public static final androidx.compose.ui.graphics.colorspace.ColorModel.Companion Companion;
   }
 
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index 844a9f2..b7fc5d5 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -345,11 +345,11 @@
     method public float getGreen();
     method public float getRed();
     method public long getValue();
-    property public final float alpha;
-    property public final float blue;
-    property public final androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace;
-    property public final float green;
-    property public final float red;
+    property @androidx.compose.runtime.Stable public final float alpha;
+    property @androidx.compose.runtime.Stable public final float blue;
+    property @androidx.compose.runtime.Stable public final androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace;
+    property @androidx.compose.runtime.Stable public final float green;
+    property @androidx.compose.runtime.Stable public final float red;
     property public final long value;
     field public static final androidx.compose.ui.graphics.Color.Companion Companion;
   }
@@ -932,7 +932,7 @@
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ColorModel {
     method public int getComponentCount();
-    property public final int componentCount;
+    property @androidx.compose.runtime.Stable public final int componentCount;
     field public static final androidx.compose.ui.graphics.colorspace.ColorModel.Companion Companion;
   }
 
diff --git a/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierParameterDetector.kt b/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierParameterDetector.kt
index d786c2f..bcc5884 100644
--- a/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierParameterDetector.kt
+++ b/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierParameterDetector.kt
@@ -75,7 +75,7 @@
             if (modifierParameter.name != ModifierParameterName) {
                 context.report(
                     ModifierParameter,
-                    node,
+                    modifierParameterElement,
                     context.getNameLocation(modifierParameterElement),
                     "$modifierName parameter should be named $ModifierParameterName",
                     LintFix.create()
@@ -91,7 +91,7 @@
             if (modifierParameter.type.canonicalText != Names.Ui.Modifier.javaFqn) {
                 context.report(
                     ModifierParameter,
-                    node,
+                    modifierParameterElement,
                     context.getNameLocation(modifierParameterElement),
                     "$modifierName parameter should have a type of $modifierName",
                     LintFix.create()
@@ -113,7 +113,7 @@
                 if (referenceExpression?.getReferencedName() != modifierName) {
                     context.report(
                         ModifierParameter,
-                        node,
+                        modifierParameterElement,
                         context.getNameLocation(modifierParameterElement),
                         "Optional $modifierName parameter should have a default value " +
                             "of `$modifierName`",
@@ -134,7 +134,7 @@
                 if (index != optionalParameterIndex) {
                     context.report(
                         ModifierParameter,
-                        node,
+                        modifierParameterElement,
                         context.getNameLocation(modifierParameterElement),
                         "$modifierName parameter should be the first optional parameter",
                         // Hard to make a lint fix for this and keep parameter formatting, so
diff --git a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierParameterDetectorTest.kt b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierParameterDetectorTest.kt
index 0c5b55e..f3aba8d 100644
--- a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierParameterDetectorTest.kt
+++ b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierParameterDetectorTest.kt
@@ -20,7 +20,6 @@
 
 import androidx.compose.lint.test.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestMode
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
 import org.junit.Test
@@ -63,7 +62,6 @@
             Stubs.Composable,
             Stubs.Modifier
         )
-            .skipTestModes(TestMode.SUPPRESSIBLE) // b/257293766
             .run()
             .expect(
                 """
@@ -105,7 +103,6 @@
             Stubs.Composable,
             Stubs.Modifier
         )
-            .skipTestModes(TestMode.SUPPRESSIBLE) // b/257293766
             .run()
             .expect(
                 """
@@ -149,7 +146,6 @@
             Stubs.Composable,
             Stubs.Modifier
         )
-            .skipTestModes(TestMode.SUPPRESSIBLE) // b/257293766
             .run()
             .expect(
                 """
@@ -191,7 +187,6 @@
             Stubs.Composable,
             Stubs.Modifier
         )
-            .skipTestModes(TestMode.SUPPRESSIBLE) // b/257293766
             .run()
             .expect(
                 """
@@ -227,7 +222,6 @@
             Stubs.Composable,
             Stubs.Modifier
         )
-            .skipTestModes(TestMode.SUPPRESSIBLE) // b/257293766
             .run()
             .expect(
                 """
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index a27a205..68fe0b6 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -1230,6 +1230,9 @@
   public final class AndroidTextPaint_androidKt {
   }
 
+  public final class EmojiCompatStatusKt {
+  }
+
   public final class Synchronization_jvmKt {
   }
 
diff --git a/compose/ui/ui-text/api/public_plus_experimental_current.txt b/compose/ui/ui-text/api/public_plus_experimental_current.txt
index 226cedf..5ee1f73 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -370,10 +370,10 @@
     method public androidx.compose.ui.text.style.TextGeometricTransform? getTextGeometricTransform();
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.SpanStyle merge(optional androidx.compose.ui.text.SpanStyle? other);
     method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.SpanStyle plus(androidx.compose.ui.text.SpanStyle other);
-    property @androidx.compose.ui.text.ExperimentalTextApi public final float alpha;
+    property @androidx.compose.ui.text.ExperimentalTextApi @androidx.compose.ui.text.ExperimentalTextApi public final float alpha;
     property public final long background;
     property public final androidx.compose.ui.text.style.BaselineShift? baselineShift;
-    property @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.graphics.Brush? brush;
+    property @androidx.compose.ui.text.ExperimentalTextApi @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.graphics.Brush? brush;
     property public final long color;
     property @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle;
     property public final androidx.compose.ui.text.font.FontFamily? fontFamily;
@@ -573,21 +573,21 @@
     method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.TextStyle plus(androidx.compose.ui.text.SpanStyle other);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.ParagraphStyle toParagraphStyle();
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.SpanStyle toSpanStyle();
-    property @androidx.compose.ui.text.ExperimentalTextApi public final float alpha;
+    property @androidx.compose.ui.text.ExperimentalTextApi @androidx.compose.ui.text.ExperimentalTextApi public final float alpha;
     property public final long background;
     property public final androidx.compose.ui.text.style.BaselineShift? baselineShift;
-    property @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.graphics.Brush? brush;
+    property @androidx.compose.ui.text.ExperimentalTextApi @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.graphics.Brush? brush;
     property public final long color;
-    property @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle;
+    property @androidx.compose.ui.text.ExperimentalTextApi @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle;
     property public final androidx.compose.ui.text.font.FontFamily? fontFamily;
     property public final String? fontFeatureSettings;
     property public final long fontSize;
     property public final androidx.compose.ui.text.font.FontStyle? fontStyle;
     property public final androidx.compose.ui.text.font.FontSynthesis? fontSynthesis;
     property public final androidx.compose.ui.text.font.FontWeight? fontWeight;
-    property @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.text.style.Hyphens? hyphens;
+    property @androidx.compose.ui.text.ExperimentalTextApi @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.text.style.Hyphens? hyphens;
     property public final long letterSpacing;
-    property @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.text.style.LineBreak? lineBreak;
+    property @androidx.compose.ui.text.ExperimentalTextApi @androidx.compose.ui.text.ExperimentalTextApi public final androidx.compose.ui.text.style.LineBreak? lineBreak;
     property public final long lineHeight;
     property public final androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle;
     property public final androidx.compose.ui.text.intl.LocaleList? localeList;
@@ -1345,6 +1345,9 @@
   public final class AndroidTextPaint_androidKt {
   }
 
+  public final class EmojiCompatStatusKt {
+  }
+
   public final class Synchronization_jvmKt {
   }
 
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index a27a205..68fe0b6 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -1230,6 +1230,9 @@
   public final class AndroidTextPaint_androidKt {
   }
 
+  public final class EmojiCompatStatusKt {
+  }
+
   public final class Synchronization_jvmKt {
   }
 
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index 49606fa..8476561 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -47,6 +47,7 @@
         implementation(libs.kotlinStdlib)
         implementation("androidx.core:core:1.7.0")
         implementation('androidx.collection:collection:1.0.0')
+        implementation("androidx.emoji2:emoji2:1.2.0")
 
         testImplementation(libs.testRules)
         testImplementation(libs.testRunner)
@@ -130,6 +131,7 @@
             androidMain.dependencies {
                 api("androidx.annotation:annotation:1.1.0")
                 implementation("androidx.core:core:1.7.0")
+                implementation("androidx.emoji2:emoji2:1.2.0")
                 implementation('androidx.collection:collection:1.0.0')
             }
 
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsicsTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsicsTest.kt
new file mode 100644
index 0000000..83f7477
--- /dev/null
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsicsTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.platform
+
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.createFontFamilyResolver
+import androidx.compose.ui.unit.Density
+import androidx.emoji2.text.EmojiCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AndroidParagraphIntrinsicsTest {
+
+    val context = InstrumentationRegistry.getInstrumentation().context
+
+    @After
+    fun cleanup() {
+        EmojiCompat.reset(null)
+        EmojiCompatStatus.setDelegateForTesting(null)
+    }
+
+    @Test
+    fun whenEmojiCompatLoads_hasStaleFontsIsTrue() {
+        val fontState = mutableStateOf(false)
+        EmojiCompatStatus.setDelegateForTesting(object : EmojiCompatStatusDelegate {
+            override val fontLoaded: State<Boolean>
+                get() = fontState
+        })
+
+        val subject = ActualParagraphIntrinsics(
+            "text",
+            TextStyle.Default,
+            listOf(),
+            listOf(),
+            Density(1f),
+            createFontFamilyResolver(context)
+        )
+
+        assertThat(subject.hasStaleResolvedFonts).isFalse()
+        fontState.value = true
+        assertThat(subject.hasStaleResolvedFonts).isTrue()
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/EmojiCompatStatusTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/EmojiCompatStatusTest.kt
new file mode 100644
index 0000000..3d15326
--- /dev/null
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/EmojiCompatStatusTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.platform
+
+import android.graphics.Typeface
+import androidx.compose.runtime.State
+import androidx.emoji2.text.EmojiCompat
+import androidx.emoji2.text.EmojiCompat.LOAD_STRATEGY_MANUAL
+import androidx.emoji2.text.EmojiCompat.MetadataRepoLoader
+import androidx.emoji2.text.MetadataRepo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class EmojiCompatStatusTest {
+
+    @Before
+    fun reset() {
+        EmojiCompat.reset(null)
+        EmojiCompatStatus.setDelegateForTesting(null)
+    }
+
+    @After
+    fun clean() {
+        EmojiCompat.reset(null)
+        EmojiCompatStatus.setDelegateForTesting(null)
+    }
+
+    @Test
+    fun nonConfiguredEc_isNotLoaded() {
+        EmojiCompat.reset(null)
+        assertThat(EmojiCompatStatus.fontLoaded.value).isFalse()
+    }
+
+    @Test
+    fun default_isNotLoaded() {
+        val (config, deferred) = makeEmojiConfig()
+        EmojiCompat.init(config)
+        assertThat(EmojiCompatStatus.fontLoaded.value).isFalse()
+        deferred.complete(null)
+    }
+
+    @Test
+    fun loading_isNotLoaded() {
+        val (config, deferred) = makeEmojiConfig()
+        val ec = EmojiCompat.init(config)
+        ec.load()
+        assertThat(EmojiCompatStatus.fontLoaded.value).isFalse()
+        deferred.complete(null)
+    }
+
+    @Test
+    fun error_isNotLoaded() {
+        val (config, deferred) = makeEmojiConfig()
+        val ec = EmojiCompat.init(config)
+        ec.load()
+        deferred.complete(null)
+        assertThat(EmojiCompatStatus.fontLoaded.value).isFalse()
+    }
+
+    @Test
+    fun loaded_isLoaded() {
+        val (config, deferred) = makeEmojiConfig()
+        val ec = EmojiCompat.init(config)
+        deferred.complete(MetadataRepo.create(Typeface.DEFAULT))
+        ec.load()
+        // query now, after init EC
+        EmojiCompatStatus.setDelegateForTesting(null)
+        EmojiCompatStatus.fontLoaded.assertTrue()
+    }
+
+    @Test
+    fun nonLoaded_toLoaded_updatesReturnState() {
+        val (config, deferred) = makeEmojiConfig()
+        val ec = EmojiCompat.init(config)
+        val state = EmojiCompatStatus.fontLoaded
+        assertThat(state.value).isFalse()
+
+        deferred.complete(MetadataRepo.create(Typeface.DEFAULT))
+        ec.load()
+
+        state.assertTrue()
+    }
+
+    @Test
+    fun nonConfigured_canLoadLater() {
+        EmojiCompat.reset(null)
+        val initialFontLoad = EmojiCompatStatus.fontLoaded
+        assertThat(initialFontLoad.value).isFalse()
+
+        val (config, deferred) = makeEmojiConfig()
+        val ec = EmojiCompat.init(config)
+        deferred.complete(MetadataRepo.create(Typeface.DEFAULT))
+        ec.load()
+
+        EmojiCompatStatus.fontLoaded.assertTrue()
+    }
+
+    private fun State<Boolean>.assertTrue() {
+        // there's too many async actors to do anything reasonable and non-flaky here. tie up the
+        // test thread until main posts the value
+        runBlocking {
+            withTimeout(1000) {
+                while (!value)
+                    delay(0)
+            }
+            assertThat(value).isTrue()
+        }
+    }
+
+    private fun makeEmojiConfig(): Pair<EmojiCompat.Config, CompletableDeferred<MetadataRepo?>> {
+        val deferred = CompletableDeferred<MetadataRepo?>(null)
+        val loader = MetadataRepoLoader { cb ->
+            CoroutineScope(Dispatchers.Default).launch {
+                val result = deferred.await()
+                if (result != null) {
+                    cb.onLoaded(result)
+                } else {
+                    cb.onFailed(IllegalStateException("No"))
+                }
+            }
+        }
+        val config = object : EmojiCompat.Config(loader) {}
+        config.setMetadataLoadStrategy(LOAD_STRATEGY_MANUAL)
+        return config to deferred
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphHelper.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphHelper.android.kt
index 9c943d0..3bcd60e 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphHelper.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphHelper.android.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.text.platform
 
 import android.graphics.Typeface
+import android.text.Spannable
 import android.text.SpannableString
 import android.text.TextPaint
 import android.text.style.CharacterStyle
@@ -41,6 +42,7 @@
 import androidx.compose.ui.text.style.TextIndent
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.isUnspecified
+import androidx.emoji2.text.EmojiCompat
 
 @OptIn(InternalPlatformTextApi::class, ExperimentalTextApi::class)
 internal fun createCharSequence(
@@ -51,16 +53,28 @@
     placeholders: List<AnnotatedString.Range<Placeholder>>,
     density: Density,
     resolveTypeface: (FontFamily?, FontWeight, FontStyle, FontSynthesis) -> Typeface,
+    useEmojiCompat: Boolean,
 ): CharSequence {
+
+    val currentText = if (useEmojiCompat && EmojiCompat.isConfigured()) {
+        EmojiCompat.get().process(text)!!
+    } else {
+        text
+    }
+
     if (spanStyles.isEmpty() &&
         placeholders.isEmpty() &&
         contextTextStyle.textIndent == TextIndent.None &&
         contextTextStyle.lineHeight.isUnspecified
     ) {
-        return text
+        return currentText
     }
 
-    val spannableString = SpannableString(text)
+    val spannableString = if (currentText is Spannable) {
+        currentText
+    } else {
+        SpannableString(currentText)
+    }
 
     // b/199939617
     // Due to a bug in the platform's native drawText stack, some CJK characters cause a bolder
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
index 8c9a75c..74dd3ce 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
@@ -65,8 +65,17 @@
 
     private val resolvedTypefaces: MutableList<TypefaceDirtyTracker> = mutableListOf()
 
+    /**
+     * If emojiCompat is used in the making of this Paragraph
+     *
+     * This value will never change
+     */
+    private val emojiCompatProcessed: Boolean = EmojiCompatStatus.fontLoaded.value
+
     override val hasStaleResolvedFonts: Boolean
-        get() = resolvedTypefaces.fastAny { it.isStaleResolvedFont }
+        get() = resolvedTypefaces.fastAny { it.isStaleResolvedFont } ||
+            (!emojiCompatProcessed &&
+                /* short-circuit this state read */ EmojiCompatStatus.fontLoaded.value)
 
     internal val textDirectionHeuristic = resolveTextDirectionHeuristics(
         style.textDirection,
@@ -74,18 +83,18 @@
     )
 
     init {
-        val resolveTypeface: (FontFamily?, FontWeight, FontStyle, FontSynthesis) -> Typeface = {
-                fontFamily, fontWeight, fontStyle, fontSynthesis ->
-            val result = fontFamilyResolver.resolve(
-                fontFamily,
-                fontWeight,
-                fontStyle,
-                fontSynthesis
-            )
-            val holder = TypefaceDirtyTracker(result)
-            resolvedTypefaces.add(holder)
-            holder.typeface
-        }
+        val resolveTypeface: (FontFamily?, FontWeight, FontStyle, FontSynthesis) -> Typeface =
+            { fontFamily, fontWeight, fontStyle, fontSynthesis ->
+                val result = fontFamilyResolver.resolve(
+                    fontFamily,
+                    fontWeight,
+                    fontStyle,
+                    fontSynthesis
+                )
+                val holder = TypefaceDirtyTracker(result)
+                resolvedTypefaces.add(holder)
+                holder.typeface
+            }
 
         val notAppliedStyle = textPaint.applySpanStyle(
             style = style.toSpanStyle(),
@@ -109,6 +118,7 @@
             placeholders = placeholders,
             density = density,
             resolveTypeface = resolveTypeface,
+            useEmojiCompat = emojiCompatProcessed
         )
 
         layoutIntrinsics = LayoutIntrinsics(charSequence, textPaint, textDirectionHeuristic)
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/EmojiCompatStatus.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/EmojiCompatStatus.kt
new file mode 100644
index 0000000..b17a3a8
--- /dev/null
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/EmojiCompatStatus.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.platform
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.emoji2.text.EmojiCompat
+
+/**
+ * Tests may provide alternative global implementations for [EmojiCompatStatus] using this delegate.
+ */
+internal interface EmojiCompatStatusDelegate {
+    val fontLoaded: State<Boolean>
+}
+
+/**
+ * Used for observing emojicompat font loading status from compose.
+ */
+internal object EmojiCompatStatus : EmojiCompatStatusDelegate {
+    private var delegate: EmojiCompatStatusDelegate = DefaultImpl()
+
+    /**
+     * True if the emoji2 font is currently loaded and processing will be successful
+     *
+     * False when emoji2 may complete loading in the future.
+     */
+    override val fontLoaded: State<Boolean>
+        get() = delegate.fontLoaded
+
+    /**
+     * Do not call.
+     *
+     * This is for tests that want to control EmojiCompatStatus behavior.
+     */
+    @VisibleForTesting
+    internal fun setDelegateForTesting(newDelegate: EmojiCompatStatusDelegate?) {
+        delegate = newDelegate ?: DefaultImpl()
+    }
+}
+
+/**
+ * is-a state, but doesn't cause an observation when read
+ */
+private class ImmutableBool(override val value: Boolean) : State<Boolean>
+private val Falsey = ImmutableBool(false)
+
+private class DefaultImpl : EmojiCompatStatusDelegate {
+
+    private var loadState: State<Boolean>?
+
+    init {
+        loadState = if (EmojiCompat.isConfigured()) {
+            getFontLoadState()
+        } else {
+            // EC isn't configured yet, will check again in getter
+            null
+        }
+    }
+
+    override val fontLoaded: State<Boolean>
+        get() = if (loadState != null) {
+            loadState!!
+        } else {
+            // EC wasn't configured last time, check again and update loadState if it's ready
+            if (EmojiCompat.isConfigured()) {
+                loadState = getFontLoadState()
+                loadState!!
+            } else {
+                // ec disabled path
+                // no observations allowed, this is pre init
+                Falsey
+            }
+        }
+
+    private fun getFontLoadState(): State<Boolean> {
+        val ec = EmojiCompat.get()
+        return if (ec.loadState == EmojiCompat.LOAD_STATE_SUCCEEDED) {
+            ImmutableBool(true)
+        } else {
+            val mutableLoaded = mutableStateOf(false)
+            val initCallback = object : EmojiCompat.InitCallback() {
+                override fun onInitialized() {
+                    mutableLoaded.value = true // update previous observers
+                    loadState = ImmutableBool(true) // never observe again
+                }
+
+                override fun onFailed(throwable: Throwable?) {
+                    loadState = Falsey // never observe again
+                }
+            }
+            ec.registerInitCallback(initCallback)
+            mutableLoaded
+        }
+    }
+}
diff --git a/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt b/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
index adea0bf..e99e3fb 100644
--- a/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
+++ b/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
@@ -19,7 +19,6 @@
 import android.app.Activity
 import android.os.Build
 import android.os.Bundle
-import androidx.compose.animation.core.InternalAnimationApi
 import androidx.compose.ui.tooling.animation.AnimateXAsStateComposeAnimation
 import androidx.compose.ui.tooling.animation.PreviewAnimationClock
 import androidx.compose.ui.tooling.animation.UnsupportedComposeAnimation
@@ -34,7 +33,6 @@
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -137,7 +135,7 @@
             assertTrue(clock.animatedVisibilityClocks.isEmpty())
         }
 
-        waitFor("Composable to have animations", 1, TimeUnit.SECONDS) {
+        waitFor(1, TimeUnit.SECONDS) {
             // Handle the case where onLayout was called too soon. Calling requestLayout will
             // make sure onLayout will be called again.
             composeViewAdapter.requestLayout()
@@ -152,22 +150,43 @@
 
     @Test
     fun transitionAnimatedVisibilityIsTrackedAsTransition() {
-        checkTransitionIsSubscribed("TransitionAnimatedVisibilityPreview", "transition.AV")
+        checkAnimationsAreSubscribed(
+            "TransitionAnimatedVisibilityPreview",
+            emptyList(),
+            listOf("transition.AV")
+        )
     }
 
     @Test
-    fun animatedContentIsSubscribed() {
-        checkAnimationsAreSubscribed("AnimatedContentPreview", listOf("AnimatedContent"))
+    fun animatedContentIsNotSubscribed() {
+        checkAnimationsAreSubscribed("AnimatedContentPreview")
+    }
+
+    @Test
+    fun animatedContentAndTransitionIsSubscribed() {
+        checkAnimationsAreSubscribed(
+            "AnimatedContentAndTransitionPreview",
+            listOf("AnimatedContent"),
+            listOf("checkBoxAnim")
+        )
     }
 
     @Test
     fun transitionAnimationsAreSubscribedToTheClock() {
-        checkTransitionIsSubscribed("TransitionPreview", "checkBoxAnim")
+        checkAnimationsAreSubscribed(
+            "TransitionPreview",
+            emptyList(),
+            listOf("checkBoxAnim")
+        )
     }
 
     @Test
     fun transitionAnimationsWithSubcomposition() {
-        checkTransitionIsSubscribed("TransitionWithScaffoldPreview", "checkBoxAnim")
+        checkAnimationsAreSubscribed(
+            "TransitionWithScaffoldPreview",
+            emptyList(),
+            listOf("checkBoxAnim")
+        )
     }
 
     @Test
@@ -197,28 +216,68 @@
     }
 
     @Test
-    fun animateContentSizeIsSubscribed() {
-        checkAnimationsAreSubscribed("AnimateContentSizePreview", listOf("animateContentSize"))
+    fun animateContentSizeIsNotSubscribed() {
+        checkAnimationsAreSubscribed("AnimateContentSizePreview")
+    }
+
+    @Test
+    fun animateContentSizeAndTransitionIsSubscribed() {
+        checkAnimationsAreSubscribed(
+            "AnimateContentSizeAndTransitionPreview",
+            listOf("animateContentSize"),
+            listOf("checkBoxAnim")
+        )
     }
 
     @Test
     fun crossFadeIsSubscribed() {
-        checkTransitionIsSubscribed("CrossFadePreview", "Crossfade")
+        checkAnimationsAreSubscribed(
+            "CrossFadePreview",
+            emptyList(),
+            listOf("Crossfade")
+        )
     }
 
     @Test
-    fun targetBasedAnimationIsSubscribed() {
-        checkAnimationsAreSubscribed("TargetBasedAnimationPreview", listOf("TargetBasedAnimation"))
+    fun targetBasedAnimationIsNotSubscribed() {
+        checkAnimationsAreSubscribed("TargetBasedAnimationPreview")
     }
 
     @Test
-    fun decayAnimationIsSubscribed() {
-        checkAnimationsAreSubscribed("DecayAnimationPreview", listOf("DecayAnimation"))
+    fun decayAnimationIsNotSubscribed() {
+        checkAnimationsAreSubscribed("DecayAnimationPreview")
     }
 
     @Test
-    fun infiniteTransitionIsSubscribed() {
-        checkAnimationsAreSubscribed("InfiniteTransitionPreview", listOf("InfiniteTransition"))
+    fun infiniteTransitionIsNotSubscribed() {
+        checkAnimationsAreSubscribed("InfiniteTransitionPreview")
+    }
+
+    @Test
+    fun targetBasedAndTransitionIsSubscribed() {
+        checkAnimationsAreSubscribed(
+            "TargetBasedAndTransitionPreview",
+            listOf("TargetBasedAnimation"),
+            listOf("checkBoxAnim")
+        )
+    }
+
+    @Test
+    fun decayAndTransitionIsSubscribed() {
+        checkAnimationsAreSubscribed(
+            "DecayAndTransitionPreview",
+            listOf("DecayAnimation"),
+            listOf("checkBoxAnim")
+        )
+    }
+
+    @Test
+    fun infiniteAndTransitionIsSubscribed() {
+        checkAnimationsAreSubscribed(
+            "InfiniteAndTransitionPreview",
+            listOf("InfiniteTransition"),
+            listOf("checkBoxAnim")
+        )
     }
 
     @Test
@@ -262,7 +321,7 @@
             assertTrue(clock.animatedVisibilityClocks.isEmpty())
         }
 
-        waitFor("Composable to have animations", 5, TimeUnit.SECONDS) {
+        waitFor(5, TimeUnit.SECONDS) {
             // Handle the case where onLayout was called too soon. Calling requestLayout will
             // make sure onLayout will be called again.
             composeViewAdapter.requestLayout()
@@ -278,33 +337,6 @@
         }
     }
 
-    @OptIn(InternalAnimationApi::class)
-    private fun checkTransitionIsSubscribed(composableName: String, label: String) {
-        val clock = PreviewAnimationClock()
-
-        activityTestRule.runOnUiThread {
-            composeViewAdapter.init(
-                "androidx.compose.ui.tooling.TestAnimationPreviewKt",
-                composableName
-            )
-            composeViewAdapter.clock = clock
-            assertFalse(composeViewAdapter.hasAnimations())
-            assertTrue(clock.transitionClocks.isEmpty())
-        }
-
-        waitFor("Composable to have animations", 1, TimeUnit.SECONDS) {
-            // Handle the case where onLayout was called too soon. Calling requestLayout will
-            // make sure onLayout will be called again.
-            composeViewAdapter.requestLayout()
-            composeViewAdapter.hasAnimations()
-        }
-
-        activityTestRule.runOnUiThread {
-            val animation = clock.transitionClocks.values.single().animation
-            assertEquals(label, animation.label)
-        }
-    }
-
     @Test
     fun lineNumberMapping() {
         val viewInfos = assertRendersCorrectly(
@@ -535,7 +567,6 @@
      * timing out. The condition is evaluated on the UI thread.
      */
     private fun waitFor(
-        conditionLabel: String,
         timeout: Long,
         timeUnit: TimeUnit,
         conditionExpression: () -> Boolean
@@ -548,7 +579,8 @@
                 conditionSatisfied.set(conditionExpression())
             }
             if ((System.nanoTime() - now) > timeoutNanos) {
-                fail("Timed out while waiting for condition <$conditionLabel> to be satisfied.")
+                // Some previews are expected not to have animations.
+                return
             }
             Thread.sleep(200)
         }
diff --git a/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/TestAnimationPreview.kt b/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/TestAnimationPreview.kt
index 71c9d1d..b52329d 100644
--- a/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/TestAnimationPreview.kt
+++ b/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/TestAnimationPreview.kt
@@ -130,6 +130,13 @@
     }
 }
 
+@Preview
+@Composable
+fun AnimatedContentAndTransitionPreview() {
+    AnimatedContentPreview()
+    TransitionPreview()
+}
+
 @OptIn(ExperimentalAnimationApi::class)
 @Preview
 @Composable
@@ -317,6 +324,13 @@
 
 @Preview
 @Composable
+fun AnimateContentSizeAndTransitionPreview() {
+    AnimateContentSizePreview()
+    TransitionPreview()
+}
+
+@Preview
+@Composable
 fun TargetBasedAnimationPreview() {
     val anim = remember {
         TargetBasedAnimation(
@@ -340,6 +354,13 @@
 
 @Preview
 @Composable
+fun TargetBasedAndTransitionPreview() {
+    TargetBasedAnimationPreview()
+    TransitionPreview()
+}
+
+@Preview
+@Composable
 fun DecayAnimationPreview() {
     val anim = remember {
         DecayAnimation(
@@ -361,6 +382,13 @@
 
 @Preview
 @Composable
+fun DecayAndTransitionPreview() {
+    DecayAnimationPreview()
+    TransitionPreview()
+}
+
+@Preview
+@Composable
 fun InfiniteTransitionPreview() {
     val infiniteTransition = rememberInfiniteTransition()
     Row {
@@ -370,6 +398,13 @@
     }
 }
 
+@Preview
+@Composable
+fun InfiniteAndTransitionPreview() {
+    InfiniteTransitionPreview()
+    TransitionPreview()
+}
+
 @Composable
 fun InfiniteTransition.PulsingDot(startOffset: StartOffset) {
     val scale by animateFloat(
diff --git a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
index 4f473ff..c477089 100644
--- a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
+++ b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
@@ -314,11 +314,17 @@
         val animatedVisibilitySearch = AnimationSearch.AnimatedVisibilitySearch {
             clock.trackAnimatedVisibility(it, ::requestLayout)
         }
+
+        fun animateXAsStateSearch() =
+            if (AnimateXAsStateComposeAnimation.apiAvailable)
+                setOf(AnimationSearch.AnimateXAsStateSearch { clock.trackAnimateXAsState(it) })
+            else emptyList()
+
         // All supported animations.
         fun supportedSearch() = setOf(
             transitionSearch,
             animatedVisibilitySearch,
-        )
+        ) + animateXAsStateSearch()
 
         fun unsupportedSearch() = if (UnsupportedComposeAnimation.apiAvailable) setOf(
             animatedContentSearch,
@@ -328,16 +334,11 @@
             AnimationSearch.InfiniteTransitionSearch { clock.trackInfiniteTransition(it) }
         ) else emptyList()
 
-        fun animateXAsStateSearch() =
-            if (AnimateXAsStateComposeAnimation.apiAvailable)
-                setOf(AnimationSearch.AnimateXAsStateSearch { clock.trackAnimateXAsState(it) })
-            else emptyList()
-
-        // All unsupported animations, if API is available.
-        val extraSearch = unsupportedSearch() + animateXAsStateSearch()
+        // All supported animations
+        val supportedSearch = supportedSearch()
 
         // Animations to track in PreviewAnimationClock.
-        val setToTrack = supportedSearch() + extraSearch
+        val setToTrack = supportedSearch + unsupportedSearch()
 
         // Animations to search. animatedContentSearch is included even if it's not going to be
         // tracked as it should be excluded from transitionSearch.
@@ -360,10 +361,12 @@
             transitionSearch.animations.removeAll(animatedContentSearch.animations)
         }
 
-        hasAnimations = setToTrack.any { it.hasAnimations() }
+        // If non of supported animations are detected, unsupported animations should not be
+        // available either.
+        hasAnimations = supportedSearch.any { it.hasAnimations() }
 
         // Make the `PreviewAnimationClock` track all the transitions found.
-        if (::clock.isInitialized) {
+        if (::clock.isInitialized && hasAnimations) {
             setToTrack.forEach { it.track() }
         }
     }
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt
index 22c19dd..a522d49 100644
--- a/compose/ui/ui-unit/api/current.txt
+++ b/compose/ui/ui-unit/api/current.txt
@@ -19,9 +19,9 @@
     method public boolean isZero();
     property public final boolean hasBoundedHeight;
     property public final boolean hasBoundedWidth;
-    property public final boolean hasFixedHeight;
-    property public final boolean hasFixedWidth;
-    property public final boolean isZero;
+    property @androidx.compose.runtime.Stable public final boolean hasFixedHeight;
+    property @androidx.compose.runtime.Stable public final boolean hasFixedWidth;
+    property @androidx.compose.runtime.Stable public final boolean isZero;
     property public final int maxHeight;
     property public final int maxWidth;
     property public final int minHeight;
@@ -136,8 +136,8 @@
     method public float getY();
     method @androidx.compose.runtime.Stable public inline operator long minus(long other);
     method @androidx.compose.runtime.Stable public inline operator long plus(long other);
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.unit.DpOffset.Companion Companion;
   }
 
@@ -182,8 +182,8 @@
     method @androidx.compose.runtime.Stable public inline operator long plus(long other);
     method @androidx.compose.runtime.Stable public operator long times(int other);
     method @androidx.compose.runtime.Stable public operator long times(float other);
-    property public final float height;
-    property public final float width;
+    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final float width;
     field public static final androidx.compose.ui.unit.DpSize.Companion Companion;
   }
 
@@ -206,8 +206,8 @@
     method @androidx.compose.runtime.Stable public operator long rem(int operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public inline operator long unaryMinus();
-    property public final int x;
-    property public final int y;
+    property @androidx.compose.runtime.Stable public final int x;
+    property @androidx.compose.runtime.Stable public final int y;
     field public static final androidx.compose.ui.unit.IntOffset.Companion Companion;
   }
 
@@ -267,18 +267,18 @@
     property public final long center;
     property public final long centerLeft;
     property public final long centerRight;
-    property public final int height;
-    property public final boolean isEmpty;
+    property @androidx.compose.runtime.Stable public final int height;
+    property @androidx.compose.runtime.Stable public final boolean isEmpty;
     property public final int left;
     property public final int maxDimension;
     property public final int minDimension;
     property public final int right;
-    property public final long size;
+    property @androidx.compose.runtime.Stable public final long size;
     property public final int top;
     property public final long topCenter;
     property public final long topLeft;
     property public final long topRight;
-    property public final int width;
+    property @androidx.compose.runtime.Stable public final int width;
     field public static final androidx.compose.ui.unit.IntRect.Companion Companion;
   }
 
@@ -301,8 +301,8 @@
     method public int getHeight();
     method public int getWidth();
     method @androidx.compose.runtime.Stable public operator long times(int other);
-    property public final int height;
-    property public final int width;
+    property @androidx.compose.runtime.Stable public final int height;
+    property @androidx.compose.runtime.Stable public final int width;
     field public static final androidx.compose.ui.unit.IntSize.Companion Companion;
   }
 
@@ -393,8 +393,8 @@
     method @androidx.compose.runtime.Stable public operator long rem(float operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.unit.Velocity.Companion Companion;
   }
 
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 a529f45..e59af0e 100644
--- a/compose/ui/ui-unit/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-unit/api/public_plus_experimental_current.txt
@@ -19,9 +19,9 @@
     method public boolean isZero();
     property public final boolean hasBoundedHeight;
     property public final boolean hasBoundedWidth;
-    property public final boolean hasFixedHeight;
-    property public final boolean hasFixedWidth;
-    property public final boolean isZero;
+    property @androidx.compose.runtime.Stable public final boolean hasFixedHeight;
+    property @androidx.compose.runtime.Stable public final boolean hasFixedWidth;
+    property @androidx.compose.runtime.Stable public final boolean isZero;
     property public final int maxHeight;
     property public final int maxWidth;
     property public final int minHeight;
@@ -136,8 +136,8 @@
     method public float getY();
     method @androidx.compose.runtime.Stable public inline operator long minus(long other);
     method @androidx.compose.runtime.Stable public inline operator long plus(long other);
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.unit.DpOffset.Companion Companion;
   }
 
@@ -182,8 +182,8 @@
     method @androidx.compose.runtime.Stable public inline operator long plus(long other);
     method @androidx.compose.runtime.Stable public operator long times(int other);
     method @androidx.compose.runtime.Stable public operator long times(float other);
-    property public final float height;
-    property public final float width;
+    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final float width;
     field public static final androidx.compose.ui.unit.DpSize.Companion Companion;
   }
 
@@ -209,8 +209,8 @@
     method @androidx.compose.runtime.Stable public operator long rem(int operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public inline operator long unaryMinus();
-    property public final int x;
-    property public final int y;
+    property @androidx.compose.runtime.Stable public final int x;
+    property @androidx.compose.runtime.Stable public final int y;
     field public static final androidx.compose.ui.unit.IntOffset.Companion Companion;
   }
 
@@ -270,18 +270,18 @@
     property public final long center;
     property public final long centerLeft;
     property public final long centerRight;
-    property public final int height;
-    property public final boolean isEmpty;
+    property @androidx.compose.runtime.Stable public final int height;
+    property @androidx.compose.runtime.Stable public final boolean isEmpty;
     property public final int left;
     property public final int maxDimension;
     property public final int minDimension;
     property public final int right;
-    property public final long size;
+    property @androidx.compose.runtime.Stable public final long size;
     property public final int top;
     property public final long topCenter;
     property public final long topLeft;
     property public final long topRight;
-    property public final int width;
+    property @androidx.compose.runtime.Stable public final int width;
     field public static final androidx.compose.ui.unit.IntRect.Companion Companion;
   }
 
@@ -304,8 +304,8 @@
     method public int getHeight();
     method public int getWidth();
     method @androidx.compose.runtime.Stable public operator long times(int other);
-    property public final int height;
-    property public final int width;
+    property @androidx.compose.runtime.Stable public final int height;
+    property @androidx.compose.runtime.Stable public final int width;
     field public static final androidx.compose.ui.unit.IntSize.Companion Companion;
   }
 
@@ -397,8 +397,8 @@
     method @androidx.compose.runtime.Stable public operator long rem(float operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.unit.Velocity.Companion Companion;
   }
 
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt
index 3b5fb53..1c6a4b8 100644
--- a/compose/ui/ui-unit/api/restricted_current.txt
+++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -19,9 +19,9 @@
     method public boolean isZero();
     property public final boolean hasBoundedHeight;
     property public final boolean hasBoundedWidth;
-    property public final boolean hasFixedHeight;
-    property public final boolean hasFixedWidth;
-    property public final boolean isZero;
+    property @androidx.compose.runtime.Stable public final boolean hasFixedHeight;
+    property @androidx.compose.runtime.Stable public final boolean hasFixedWidth;
+    property @androidx.compose.runtime.Stable public final boolean isZero;
     property public final int maxHeight;
     property public final int maxWidth;
     property public final int minHeight;
@@ -136,8 +136,8 @@
     method public float getY();
     method @androidx.compose.runtime.Stable public inline operator long minus(long other);
     method @androidx.compose.runtime.Stable public inline operator long plus(long other);
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.unit.DpOffset.Companion Companion;
   }
 
@@ -182,8 +182,8 @@
     method @androidx.compose.runtime.Stable public inline operator long plus(long other);
     method @androidx.compose.runtime.Stable public operator long times(int other);
     method @androidx.compose.runtime.Stable public operator long times(float other);
-    property public final float height;
-    property public final float width;
+    property @androidx.compose.runtime.Stable public final float height;
+    property @androidx.compose.runtime.Stable public final float width;
     field public static final androidx.compose.ui.unit.DpSize.Companion Companion;
   }
 
@@ -206,8 +206,8 @@
     method @androidx.compose.runtime.Stable public operator long rem(int operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public inline operator long unaryMinus();
-    property public final int x;
-    property public final int y;
+    property @androidx.compose.runtime.Stable public final int x;
+    property @androidx.compose.runtime.Stable public final int y;
     field public static final androidx.compose.ui.unit.IntOffset.Companion Companion;
   }
 
@@ -267,18 +267,18 @@
     property public final long center;
     property public final long centerLeft;
     property public final long centerRight;
-    property public final int height;
-    property public final boolean isEmpty;
+    property @androidx.compose.runtime.Stable public final int height;
+    property @androidx.compose.runtime.Stable public final boolean isEmpty;
     property public final int left;
     property public final int maxDimension;
     property public final int minDimension;
     property public final int right;
-    property public final long size;
+    property @androidx.compose.runtime.Stable public final long size;
     property public final int top;
     property public final long topCenter;
     property public final long topLeft;
     property public final long topRight;
-    property public final int width;
+    property @androidx.compose.runtime.Stable public final int width;
     field public static final androidx.compose.ui.unit.IntRect.Companion Companion;
   }
 
@@ -301,8 +301,8 @@
     method public int getHeight();
     method public int getWidth();
     method @androidx.compose.runtime.Stable public operator long times(int other);
-    property public final int height;
-    property public final int width;
+    property @androidx.compose.runtime.Stable public final int height;
+    property @androidx.compose.runtime.Stable public final int width;
     field public static final androidx.compose.ui.unit.IntSize.Companion Companion;
   }
 
@@ -341,6 +341,7 @@
     method public inline operator long unaryMinus();
     property public final boolean isEm;
     property public final boolean isSp;
+    property @kotlin.PublishedApi internal final long rawType;
     property public final long type;
     property public final float value;
     field public static final androidx.compose.ui.unit.TextUnit.Companion Companion;
@@ -397,8 +398,8 @@
     method @androidx.compose.runtime.Stable public operator long rem(float operand);
     method @androidx.compose.runtime.Stable public operator long times(float operand);
     method @androidx.compose.runtime.Stable public operator long unaryMinus();
-    property public final float x;
-    property public final float y;
+    property @androidx.compose.runtime.Stable public final float x;
+    property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.unit.Velocity.Companion Companion;
   }
 
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 732988f..8eac76e 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -828,6 +828,17 @@
     property public abstract int inputMode;
   }
 
+  public interface ScrollContainerInfo {
+    method public boolean canScrollHorizontally();
+    method public boolean canScrollVertically();
+  }
+
+  public final class ScrollContainerInfoKt {
+    method public static boolean canScroll(androidx.compose.ui.input.ScrollContainerInfo);
+    method public static androidx.compose.ui.Modifier consumeScrollContainerInfo(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.ScrollContainerInfo,kotlin.Unit> consumer);
+    method public static androidx.compose.ui.Modifier provideScrollContainerInfo(androidx.compose.ui.Modifier, androidx.compose.ui.input.ScrollContainerInfo scrollContainerInfo);
+  }
+
 }
 
 package androidx.compose.ui.input.key {
@@ -2111,8 +2122,8 @@
     method public float getScaleX();
     method public float getScaleY();
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    property public final float scaleX;
-    property public final float scaleY;
+    property @androidx.compose.runtime.Stable public final float scaleX;
+    property @androidx.compose.runtime.Stable public final float scaleY;
     field public static final androidx.compose.ui.layout.ScaleFactor.Companion Companion;
   }
 
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 69b9283..3dd72e6 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -473,8 +473,8 @@
     property public abstract boolean canFocus;
     property public default androidx.compose.ui.focus.FocusRequester down;
     property public default androidx.compose.ui.focus.FocusRequester end;
-    property @androidx.compose.ui.ExperimentalComposeUiApi public default kotlin.jvm.functions.Function1<androidx.compose.ui.focus.FocusDirection,androidx.compose.ui.focus.FocusRequester> enter;
-    property @androidx.compose.ui.ExperimentalComposeUiApi public default kotlin.jvm.functions.Function1<androidx.compose.ui.focus.FocusDirection,androidx.compose.ui.focus.FocusRequester> exit;
+    property @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.ExperimentalComposeUiApi public default kotlin.jvm.functions.Function1<androidx.compose.ui.focus.FocusDirection,androidx.compose.ui.focus.FocusRequester> enter;
+    property @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.ExperimentalComposeUiApi public default kotlin.jvm.functions.Function1<androidx.compose.ui.focus.FocusDirection,androidx.compose.ui.focus.FocusRequester> exit;
     property public default androidx.compose.ui.focus.FocusRequester left;
     property public default androidx.compose.ui.focus.FocusRequester next;
     property public default androidx.compose.ui.focus.FocusRequester previous;
@@ -954,6 +954,17 @@
     property public abstract int inputMode;
   }
 
+  public interface ScrollContainerInfo {
+    method public boolean canScrollHorizontally();
+    method public boolean canScrollVertically();
+  }
+
+  public final class ScrollContainerInfoKt {
+    method public static boolean canScroll(androidx.compose.ui.input.ScrollContainerInfo);
+    method public static androidx.compose.ui.Modifier consumeScrollContainerInfo(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.ScrollContainerInfo,kotlin.Unit> consumer);
+    method public static androidx.compose.ui.Modifier provideScrollContainerInfo(androidx.compose.ui.Modifier, androidx.compose.ui.input.ScrollContainerInfo scrollContainerInfo);
+  }
+
 }
 
 package androidx.compose.ui.input.key {
@@ -1806,12 +1817,12 @@
     method public long getUptimeMillis();
     method public boolean isConsumed();
     property @Deprecated public final androidx.compose.ui.input.pointer.ConsumedData consumed;
-    property @androidx.compose.ui.ExperimentalComposeUiApi public final java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical;
+    property @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.ExperimentalComposeUiApi public final java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical;
     property public final long id;
     property public final boolean isConsumed;
     property public final long position;
     property public final boolean pressed;
-    property @androidx.compose.ui.ExperimentalComposeUiApi public final float pressure;
+    property @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.ExperimentalComposeUiApi public final float pressure;
     property public final long previousPosition;
     property public final boolean previousPressed;
     property public final long previousUptimeMillis;
@@ -1831,7 +1842,7 @@
     method public abstract void onCancel();
     method public abstract void onPointerEvent(androidx.compose.ui.input.pointer.PointerEvent pointerEvent, androidx.compose.ui.input.pointer.PointerEventPass pass, long bounds);
     property public boolean interceptOutOfBoundsChildEvents;
-    property @androidx.compose.ui.ExperimentalComposeUiApi public boolean shareWithSiblings;
+    property @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.ExperimentalComposeUiApi public boolean shareWithSiblings;
     property public final long size;
   }
 
@@ -2266,7 +2277,7 @@
     method public final void placeRelativeWithLayer(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
     method public final void placeWithLayer(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
     method public final void placeWithLayer(androidx.compose.ui.layout.Placeable, long position, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
-    property @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.layout.LayoutCoordinates? coordinates;
+    property @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.layout.LayoutCoordinates? coordinates;
     property protected abstract androidx.compose.ui.unit.LayoutDirection parentLayoutDirection;
     property protected abstract int parentWidth;
   }
@@ -2308,8 +2319,8 @@
     method public float getScaleX();
     method public float getScaleY();
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    property public final float scaleX;
-    property public final float scaleY;
+    property @androidx.compose.runtime.Stable public final float scaleX;
+    property @androidx.compose.runtime.Stable public final float scaleY;
     field public static final androidx.compose.ui.layout.ScaleFactor.Companion Companion;
   }
 
@@ -2818,7 +2829,7 @@
   @androidx.compose.ui.ExperimentalComposeUiApi public final class LocalSoftwareKeyboardController {
     method @androidx.compose.runtime.Composable public androidx.compose.ui.platform.SoftwareKeyboardController? getCurrent();
     method public infix androidx.compose.runtime.ProvidedValue<androidx.compose.ui.platform.SoftwareKeyboardController> provides(androidx.compose.ui.platform.SoftwareKeyboardController softwareKeyboardController);
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.platform.SoftwareKeyboardController? current;
+    property @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi public final androidx.compose.ui.platform.SoftwareKeyboardController? current;
     field public static final androidx.compose.ui.platform.LocalSoftwareKeyboardController INSTANCE;
   }
 
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index db61359..7540593 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -828,6 +828,17 @@
     property public abstract int inputMode;
   }
 
+  public interface ScrollContainerInfo {
+    method public boolean canScrollHorizontally();
+    method public boolean canScrollVertically();
+  }
+
+  public final class ScrollContainerInfoKt {
+    method public static boolean canScroll(androidx.compose.ui.input.ScrollContainerInfo);
+    method public static androidx.compose.ui.Modifier consumeScrollContainerInfo(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.ScrollContainerInfo,kotlin.Unit> consumer);
+    method public static androidx.compose.ui.Modifier provideScrollContainerInfo(androidx.compose.ui.Modifier, androidx.compose.ui.input.ScrollContainerInfo scrollContainerInfo);
+  }
+
 }
 
 package androidx.compose.ui.input.key {
@@ -2114,8 +2125,8 @@
     method public float getScaleX();
     method public float getScaleY();
     method @androidx.compose.runtime.Stable public operator long times(float operand);
-    property public final float scaleX;
-    property public final float scaleY;
+    property @androidx.compose.runtime.Stable public final float scaleX;
+    property @androidx.compose.runtime.Stable public final float scaleY;
     field public static final androidx.compose.ui.layout.ScaleFactor.Companion Companion;
   }
 
diff --git a/compose/ui/ui/benchmark/build.gradle b/compose/ui/ui/benchmark/build.gradle
index 0b58476..5cc4f13 100644
--- a/compose/ui/ui/benchmark/build.gradle
+++ b/compose/ui/ui/benchmark/build.gradle
@@ -24,6 +24,7 @@
 
 dependencies {
 
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.5.1")
     androidTestImplementation("androidx.activity:activity-compose:1.3.1")
     androidTestImplementation(project(":benchmark:benchmark-junit4"))
     androidTestImplementation(project(":compose:foundation:foundation"))
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/LifecycleAwareWindowRecomposerBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/LifecycleAwareWindowRecomposerBenchmark.kt
index ef060ab..082292b 100644
--- a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/LifecycleAwareWindowRecomposerBenchmark.kt
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/LifecycleAwareWindowRecomposerBenchmark.kt
@@ -24,8 +24,7 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.platform.createLifecycleAwareWindowRecomposer
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
-import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.annotation.UiThreadTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -49,23 +48,14 @@
     @UiThreadTest
     fun createRecomposer() {
         val rootView = rule.activityTestRule.activity.window.decorView.rootView
-        val lifecycle = object : Lifecycle() {
-            override fun addObserver(observer: LifecycleObserver) {
-                if (observer is LifecycleEventObserver) {
-                    observer.onStateChanged({ this }, Event.ON_CREATE)
-                }
-            }
-
-            override fun removeObserver(observer: LifecycleObserver) {}
-            override fun getCurrentState(): State = State.CREATED
-        }
+        val lifecycleOwner = TestLifecycleOwner(Lifecycle.State.CREATED)
         var view: View? = null
         rule.benchmarkRule.measureRepeated {
             runWithTimingDisabled {
                 view = View(rule.activityTestRule.activity)
                 (rootView as ViewGroup).addView(view)
             }
-            view!!.createLifecycleAwareWindowRecomposer(lifecycle = lifecycle)
+            view!!.createLifecycleAwareWindowRecomposer(lifecycle = lifecycleOwner.lifecycle)
             runWithTimingDisabled {
                 (rootView as ViewGroup).removeAllViews()
                 view = null
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index a834100..4944ff2 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -167,7 +167,7 @@
                 implementation(libs.kotlinCoroutinesAndroid)
 
                 implementation("androidx.activity:activity-ktx:1.5.1")
-                implementation("androidx.core:core:1.5.0")
+                implementation("androidx.core:core:1.9.0")
                 implementation('androidx.collection:collection:1.0.0')
                 implementation("androidx.customview:customview-poolingcontainer:1.0.0")
                 implementation("androidx.savedstate:savedstate-ktx:1.2.0")
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ScrollableContainerSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ScrollableContainerSample.kt
new file mode 100644
index 0000000..7146d12
--- /dev/null
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ScrollableContainerSample.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+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.Modifier
+import androidx.compose.ui.input.canScroll
+import androidx.compose.ui.input.consumeScrollContainerInfo
+import androidx.compose.ui.input.pointer.pointerInput
+import java.util.concurrent.TimeoutException
+import kotlinx.coroutines.withTimeout
+
+@Sampled
+@Composable
+fun ScrollableContainerSample() {
+    var isParentScrollable by remember { mutableStateOf({ false }) }
+
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+
+        Box(modifier = Modifier.consumeScrollContainerInfo {
+            isParentScrollable = { it?.canScroll() == true }
+        }) {
+            Box(Modifier.pointerInput(Unit) {
+                detectTapGestures(
+                    onPress = {
+                        // If there is an ancestor that handles drag events, this press might
+                        // become a drag so delay any work
+                        val doWork = !isParentScrollable() || try {
+                            withTimeout(100) { tryAwaitRelease() }
+                        } catch (e: TimeoutException) {
+                            true
+                        }
+                        if (doWork) println("Do work")
+                    })
+            })
+        }
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/TextInputServiceAndroidOnStateUpdateTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/TextInputServiceAndroidOnStateUpdateTest.kt
index 16f6740..94300f6 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/TextInputServiceAndroidOnStateUpdateTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/TextInputServiceAndroidOnStateUpdateTest.kt
@@ -70,8 +70,8 @@
             newValue = newValue
         )
 
-        verify(inputMethodManager, times(1)).restartInput(any())
-        verify(inputMethodManager, never()).updateSelection(any(), any(), any(), any(), any())
+        verify(inputMethodManager, times(1)).restartInput()
+        verify(inputMethodManager, never()).updateSelection(any(), any(), any(), any())
 
         assertThat(inputConnection.mTextFieldValue).isEqualTo(newValue)
         assertThat(textInputService.state).isEqualTo(newValue)
@@ -85,8 +85,8 @@
             newValue = newValue
         )
 
-        verify(inputMethodManager, times(1)).restartInput(any())
-        verify(inputMethodManager, never()).updateSelection(any(), any(), any(), any(), any())
+        verify(inputMethodManager, times(1)).restartInput()
+        verify(inputMethodManager, never()).updateSelection(any(), any(), any(), any())
 
         assertThat(inputConnection.mTextFieldValue).isEqualTo(newValue)
         assertThat(textInputService.state).isEqualTo(newValue)
@@ -100,8 +100,8 @@
             newValue = newValue
         )
 
-        verify(inputMethodManager, never()).restartInput(any())
-        verify(inputMethodManager, times(1)).updateSelection(any(), any(), any(), any(), any())
+        verify(inputMethodManager, never()).restartInput()
+        verify(inputMethodManager, times(1)).updateSelection(any(), any(), any(), any())
 
         assertThat(inputConnection.mTextFieldValue).isEqualTo(newValue)
         assertThat(textInputService.state).isEqualTo(newValue)
@@ -121,8 +121,8 @@
             newValue = value
         )
 
-        verify(inputMethodManager, never()).restartInput(any())
-        verify(inputMethodManager, times(1)).updateSelection(any(), any(), any(), any(), any())
+        verify(inputMethodManager, never()).restartInput()
+        verify(inputMethodManager, times(1)).updateSelection(any(), any(), any(), any())
 
         assertThat(inputConnection.mTextFieldValue).isEqualTo(value)
         assertThat(textInputService.state).isEqualTo(value)
@@ -148,9 +148,9 @@
             newValue = value2
         )
 
-        verify(inputMethodManager, never()).restartInput(any())
+        verify(inputMethodManager, never()).restartInput()
         verify(inputMethodManager, times(1)).updateSelection(
-            any(), eq(value2.selection.min), eq(value2.selection.max), eq(-1), eq(-1)
+            eq(value2.selection.min), eq(value2.selection.max), eq(-1), eq(-1)
         )
     }
 
@@ -162,8 +162,8 @@
             newValue = newValue
         )
 
-        verify(inputMethodManager, never()).restartInput(any())
-        verify(inputMethodManager, times(1)).updateSelection(any(), any(), any(), any(), any())
+        verify(inputMethodManager, never()).restartInput()
+        verify(inputMethodManager, times(1)).updateSelection(any(), any(), any(), any())
 
         assertThat(inputConnection.mTextFieldValue).isEqualTo(newValue)
         assertThat(textInputService.state).isEqualTo(newValue)
@@ -177,8 +177,8 @@
             newValue = value
         )
 
-        verify(inputMethodManager, never()).restartInput(any())
-        verify(inputMethodManager, never()).updateSelection(any(), any(), any(), any(), any())
+        verify(inputMethodManager, never()).restartInput()
+        verify(inputMethodManager, never()).updateSelection(any(), any(), any(), any())
 
         assertThat(inputConnection.mTextFieldValue).isEqualTo(value)
         assertThat(textInputService.state).isEqualTo(value)
@@ -192,8 +192,8 @@
             newValue = value
         )
 
-        verify(inputMethodManager, never()).restartInput(any())
-        verify(inputMethodManager, never()).updateSelection(any(), any(), any(), any(), any())
+        verify(inputMethodManager, never()).restartInput()
+        verify(inputMethodManager, never()).updateSelection(any(), any(), any(), any())
 
         // recreate the connection
         inputConnection = textInputService.createInputConnection(EditorInfo())
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/nestedscroll/ScrollContainerInfoTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/nestedscroll/ScrollContainerInfoTest.kt
new file mode 100644
index 0000000..4bf39eb
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/nestedscroll/ScrollContainerInfoTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.compose.ui.input.ScrollContainerInfo
+import androidx.compose.ui.input.canScroll
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ScrollContainerInfoTest {
+
+    @Test
+    fun canScroll_horizontal() {
+        val subject = Subject(horizontal = true)
+
+        assertThat(subject.canScroll()).isTrue()
+    }
+
+    @Test
+    fun canScroll_vertical() {
+        val subject = Subject(vertical = true)
+
+        assertThat(subject.canScroll()).isTrue()
+    }
+
+    @Test
+    fun canScroll_both() {
+        val subject = Subject(horizontal = true, vertical = true)
+
+        assertThat(subject.canScroll()).isTrue()
+    }
+
+    @Test
+    fun canScroll_neither() {
+        val subject = Subject(horizontal = false, vertical = false)
+
+        assertThat(subject.canScroll()).isFalse()
+    }
+
+    class Subject(
+        private val horizontal: Boolean = false,
+        private val vertical: Boolean = false,
+    ) : ScrollContainerInfo {
+        override fun canScrollHorizontally(): Boolean = horizontal
+        override fun canScrollVertically(): Boolean = vertical
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
index 0885058..7919713 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
@@ -30,6 +30,7 @@
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
+import android.widget.LinearLayout
 import android.widget.RelativeLayout
 import android.widget.TextView
 import androidx.compose.foundation.background
@@ -38,6 +39,8 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.SideEffect
@@ -52,6 +55,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.input.canScroll
+import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalDensity
@@ -86,6 +91,7 @@
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
+import kotlin.math.roundToInt
 import org.hamcrest.CoreMatchers.endsWith
 import org.hamcrest.CoreMatchers.equalTo
 import org.hamcrest.CoreMatchers.instanceOf
@@ -93,7 +99,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import kotlin.math.roundToInt
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
@@ -637,11 +642,19 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun androidView_noClip() {
         rule.setContent {
-            Box(Modifier.fillMaxSize().background(Color.White)) {
+            Box(
+                Modifier
+                    .fillMaxSize()
+                    .background(Color.White)) {
                 with(LocalDensity.current) {
-                    Box(Modifier.requiredSize(150.toDp()).testTag("box")) {
+                    Box(
+                        Modifier
+                            .requiredSize(150.toDp())
+                            .testTag("box")) {
                         Box(
-                            Modifier.size(100.toDp(), 100.toDp()).align(AbsoluteAlignment.TopLeft)
+                            Modifier
+                                .size(100.toDp(), 100.toDp())
+                                .align(AbsoluteAlignment.TopLeft)
                         ) {
                             AndroidView(factory = { context ->
                                 object : View(context) {
@@ -667,6 +680,92 @@
         }
     }
 
+    @Test
+    fun scrollableViewGroup_propagates_shouldDelay() {
+        val scrollContainerInfo = mutableStateOf({ false })
+        rule.activityRule.scenario.onActivity { activity ->
+            val parentComposeView = ScrollingViewGroup(activity).apply {
+                addView(
+                    ComposeView(activity).apply {
+                        setContent {
+                            Box(modifier = Modifier.consumeScrollContainerInfo {
+                                scrollContainerInfo.value = { it?.canScroll() == true }
+                            })
+                        }
+                    })
+                }
+            activity.setContentView(parentComposeView)
+        }
+
+        rule.runOnIdle {
+            assertThat(scrollContainerInfo.value()).isTrue()
+        }
+    }
+
+    @Test
+    fun nonScrollableViewGroup_doesNotPropagate_shouldDelay() {
+        val scrollContainerInfo = mutableStateOf({ false })
+        rule.activityRule.scenario.onActivity { activity ->
+            val parentComposeView = FrameLayout(activity).apply {
+                addView(
+                    ComposeView(activity).apply {
+                        setContent {
+                            Box(modifier = Modifier.consumeScrollContainerInfo {
+                                scrollContainerInfo.value = { it?.canScroll() == true }
+                            })
+                        }
+                    })
+            }
+            activity.setContentView(parentComposeView)
+        }
+
+        rule.runOnIdle {
+            assertThat(scrollContainerInfo.value()).isFalse()
+        }
+    }
+
+    @Test
+    fun viewGroup_propagates_shouldDelayTrue() {
+        lateinit var layout: View
+        rule.setContent {
+            Column(Modifier.verticalScroll(rememberScrollState())) {
+                AndroidView(
+                    factory = {
+                        layout = LinearLayout(it)
+                        layout
+                    }
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            // View#isInScrollingContainer is hidden, check the parent manually.
+            val shouldDelay = (layout.parent as ViewGroup).shouldDelayChildPressedState()
+            assertThat(shouldDelay).isTrue()
+        }
+    }
+
+    @Test
+    fun viewGroup_propagates_shouldDelayFalse() {
+        lateinit var layout: View
+        rule.setContent {
+            Column {
+                AndroidView(
+                    factory = {
+                        layout = LinearLayout(it)
+                        layout
+                    }
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            // View#isInScrollingContainer is hidden, check the parent manually.
+            val shouldDelay = (layout.parent as ViewGroup).shouldDelayChildPressedState()
+            assertThat(shouldDelay).isFalse()
+        }
+    }
+
     private class StateSavingView(
         private val key: String,
         private val value: String,
@@ -698,4 +797,8 @@
             value,
             displayMetrics
         ).roundToInt()
+
+    class ScrollingViewGroup(context: Context) : FrameLayout(context) {
+        override fun shouldDelayChildPressedState() = true
+    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt
index da9ee0f..02c2c59 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt
@@ -73,6 +73,8 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SmallTest
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 import org.hamcrest.CoreMatchers.instanceOf
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -83,8 +85,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 import kotlin.math.roundToInt
 
 @MediumTest
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 2eb61e2..fefb238 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -126,6 +126,9 @@
 import androidx.compose.ui.node.Owner
 import androidx.compose.ui.node.OwnerSnapshotObserver
 import androidx.compose.ui.node.RootForTest
+import androidx.compose.ui.input.ScrollContainerInfo
+import androidx.compose.ui.input.ModifierLocalScrollContainerInfo
+import androidx.compose.ui.modifier.ModifierLocalProvider
 import androidx.compose.ui.semantics.SemanticsModifierCore
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.semantics.SemanticsOwner
@@ -220,6 +223,34 @@
         false
     }
 
+    // We don't have a way to determine direction in Android, return true for both directions.
+    private val scrollContainerInfo = object : ModifierLocalProvider<ScrollContainerInfo?> {
+        override val key = ModifierLocalScrollContainerInfo
+        override val value = object : ScrollContainerInfo {
+            // Intentionally not using [View#canScrollHorizontally], to maintain semantics of
+            // View#isInScrollingContainer
+            override fun canScrollHorizontally(): Boolean =
+                view.isInScrollableViewGroup()
+
+            // Intentionally not using [View#canScrollVertically], to maintain semantics of
+            // View#isInScrollingContainer
+            override fun canScrollVertically(): Boolean =
+                view.isInScrollableViewGroup()
+
+            // Copied from View#isInScrollingContainer() which is @hide
+            private fun View.isInScrollableViewGroup(): Boolean {
+                var p = parent
+                while (p != null && p is ViewGroup) {
+                    if (p.shouldDelayChildPressedState()) {
+                        return true
+                    }
+                    p = p.parent
+                }
+                return false
+            }
+        }
+    }
+
     private val canvasHolder = CanvasHolder()
 
     override val root = LayoutNode().also {
@@ -231,6 +262,7 @@
             .then(rotaryInputModifier)
             .then(_focusManager.modifier)
             .then(keyInputModifier)
+            .then(scrollContainerInfo)
     }
 
     override val rootForTest: RootForTest = this
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputMethodManager.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputMethodManager.kt
index 39071a16..50d67c6 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputMethodManager.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/InputMethodManager.kt
@@ -16,26 +16,33 @@
 
 package androidx.compose.ui.text.input
 
+import android.app.Activity
 import android.content.Context
-import android.os.IBinder
+import android.content.ContextWrapper
+import android.os.Build
+import android.util.Log
 import android.view.View
+import android.view.Window
 import android.view.inputmethod.ExtractedText
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.compose.ui.window.DialogWindowProvider
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
 
 internal interface InputMethodManager {
-    fun restartInput(view: View)
+    fun restartInput()
 
-    fun showSoftInput(view: View)
+    fun showSoftInput()
 
-    fun hideSoftInputFromWindow(windowToken: IBinder?)
+    fun hideSoftInput()
 
     fun updateExtractedText(
-        view: View,
         token: Int,
         extractedText: ExtractedText
     )
 
     fun updateSelection(
-        view: View,
         selectionStart: Int,
         selectionEnd: Int,
         compositionStart: Int,
@@ -47,27 +54,36 @@
  * Wrapper class to prevent depending on getSystemService and final InputMethodManager.
  * Let's us test TextInputServiceAndroid class.
  */
-internal class InputMethodManagerImpl(context: Context) : InputMethodManager {
+internal class InputMethodManagerImpl(private val view: View) : InputMethodManager {
 
     private val imm by lazy(LazyThreadSafetyMode.NONE) {
-        context.getSystemService(Context.INPUT_METHOD_SERVICE)
+        view.context.getSystemService(Context.INPUT_METHOD_SERVICE)
             as android.view.inputmethod.InputMethodManager
     }
 
-    override fun restartInput(view: View) {
+    private val helper = if (Build.VERSION.SDK_INT < 30) {
+        ImmHelper21(view)
+    } else {
+        ImmHelper30(view)
+    }
+
+    override fun restartInput() {
         imm.restartInput(view)
     }
 
-    override fun showSoftInput(view: View) {
-        imm.showSoftInput(view, 0)
+    override fun showSoftInput() {
+        if (DEBUG && !view.hasWindowFocus()) {
+            Log.d(TAG, "InputMethodManagerImpl: requesting soft input on non focused field")
+        }
+
+        helper.showSoftInput(imm)
     }
 
-    override fun hideSoftInputFromWindow(windowToken: IBinder?) {
-        imm.hideSoftInputFromWindow(windowToken, 0)
+    override fun hideSoftInput() {
+        helper.hideSoftInput(imm)
     }
 
     override fun updateExtractedText(
-        view: View,
         token: Int,
         extractedText: ExtractedText
     ) {
@@ -75,7 +91,6 @@
     }
 
     override fun updateSelection(
-        view: View,
         selectionStart: Int,
         selectionEnd: Int,
         compositionStart: Int,
@@ -83,4 +98,71 @@
     ) {
         imm.updateSelection(view, selectionStart, selectionEnd, compositionStart, compositionEnd)
     }
-}
\ No newline at end of file
+}
+
+private interface ImmHelper {
+    fun showSoftInput(imm: android.view.inputmethod.InputMethodManager)
+    fun hideSoftInput(imm: android.view.inputmethod.InputMethodManager)
+}
+
+private class ImmHelper21(private val view: View) : ImmHelper {
+
+    @DoNotInline
+    override fun showSoftInput(imm: android.view.inputmethod.InputMethodManager) {
+        view.post {
+            imm.showSoftInput(view, 0)
+        }
+    }
+
+    @DoNotInline
+    override fun hideSoftInput(imm: android.view.inputmethod.InputMethodManager) {
+        imm.hideSoftInputFromWindow(view.windowToken, 0)
+    }
+}
+
+@RequiresApi(30)
+private class ImmHelper30(private val view: View) : ImmHelper {
+
+    /**
+     * Get a [WindowInsetsControllerCompat] for the view. This returns a new instance every time,
+     * since the view may return null or not null at different times depending on window attach
+     * state.
+     */
+    private val insetsControllerCompat
+        // This can return null when, for example, the view is not attached to a window.
+        get() = view.findWindow()?.let { WindowInsetsControllerCompat(it, view) }
+
+    /**
+     * This class falls back to the legacy implementation when the window insets controller isn't
+     * available.
+     */
+    private val immHelper21: ImmHelper21
+        get() = _immHelper21 ?: ImmHelper21(view).also { _immHelper21 = it }
+    private var _immHelper21: ImmHelper21? = null
+
+    @DoNotInline
+    override fun showSoftInput(imm: android.view.inputmethod.InputMethodManager) {
+        insetsControllerCompat?.apply {
+            show(WindowInsetsCompat.Type.ime())
+        } ?: immHelper21.showSoftInput(imm)
+    }
+
+    @DoNotInline
+    override fun hideSoftInput(imm: android.view.inputmethod.InputMethodManager) {
+        insetsControllerCompat?.apply {
+            hide(WindowInsetsCompat.Type.ime())
+        } ?: immHelper21.hideSoftInput(imm)
+    }
+
+    // TODO(b/221889664) Replace with composition local when available.
+    private fun View.findWindow(): Window? =
+        (parent as? DialogWindowProvider)?.window
+            ?: context.findWindow()
+
+    private tailrec fun Context.findWindow(): Window? =
+        when (this) {
+            is Activity -> window
+            is ContextWrapper -> baseContext.findWindow()
+            else -> null
+        }
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
index 97ee5954..f234d53 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
@@ -21,7 +21,6 @@
 import android.text.TextUtils
 import android.util.Log
 import android.view.KeyEvent
-import android.view.View
 import android.view.inputmethod.CompletionInfo
 import android.view.inputmethod.CorrectionInfo
 import android.view.inputmethod.EditorInfo
@@ -94,7 +93,6 @@
     fun updateInputState(
         state: TextFieldValue,
         inputMethodManager: InputMethodManager,
-        view: View
     ) {
         if (!isActive) return
 
@@ -104,7 +102,6 @@
 
         if (extractedTextMonitorMode) {
             inputMethodManager.updateExtractedText(
-                view,
                 currentExtractedTextRequestToken,
                 state.toExtractedText()
             )
@@ -121,7 +118,7 @@
             )
         }
         inputMethodManager.updateSelection(
-            view, state.selection.min, state.selection.max, compositionStart, compositionEnd
+            state.selection.min, state.selection.max, compositionStart, compositionEnd
         )
     }
 
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
index 1dbb467..14cff951 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
@@ -95,10 +95,12 @@
      */
     private val textInputCommandChannel = Channel<TextInputCommand>(Channel.UNLIMITED)
 
-    internal constructor(view: View) : this(view, InputMethodManagerImpl(view.context))
+    internal constructor(view: View) : this(view, InputMethodManagerImpl(view))
 
     init {
-        if (DEBUG) { Log.d(TAG, "$DEBUG_CLASS.create") }
+        if (DEBUG) {
+            Log.d(TAG, "$DEBUG_CLASS.create")
+        }
     }
 
     /**
@@ -138,7 +140,9 @@
             }
         ).also {
             ics.add(WeakReference(it))
-            if (DEBUG) { Log.d(TAG, "$DEBUG_CLASS.createInputConnection: $ics") }
+            if (DEBUG) {
+                Log.d(TAG, "$DEBUG_CLASS.createInputConnection: $ics")
+            }
         }
     }
 
@@ -324,7 +328,6 @@
             if (needUpdateSelection) {
                 // updateSelection API requires -1 if there is no composition
                 inputMethodManager.updateSelection(
-                    view = view,
                     selectionStart = newValue.selection.min,
                     selectionEnd = newValue.selection.max,
                     compositionStart = state.composition?.min ?: -1,
@@ -348,7 +351,7 @@
             restartInputImmediately()
         } else {
             for (i in 0 until ics.size) {
-                ics[i].get()?.updateInputState(this.state, inputMethodManager, view)
+                ics[i].get()?.updateInputState(this.state, inputMethodManager)
             }
         }
     }
@@ -380,16 +383,16 @@
     /** Immediately restart the IME connection, bypassing the [textInputCommandChannel]. */
     private fun restartInputImmediately() {
         if (DEBUG) Log.d(TAG, "$DEBUG_CLASS.restartInputImmediately")
-        inputMethodManager.restartInput(view)
+        inputMethodManager.restartInput()
     }
 
     /** Immediately show or hide the keyboard, bypassing the [textInputCommandChannel]. */
     private fun setKeyboardVisibleImmediately(visible: Boolean) {
         if (DEBUG) Log.d(TAG, "$DEBUG_CLASS.setKeyboardVisibleImmediately(visible=$visible)")
         if (visible) {
-            inputMethodManager.showSoftInput(view)
+            inputMethodManager.showSoftInput()
         } else {
-            inputMethodManager.hideSoftInputFromWindow(view.windowToken)
+            inputMethodManager.hideSoftInput()
         }
     }
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
index 9b725ee..6032a96 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
@@ -61,9 +61,9 @@
  * Compose and there is no corresponding Compose API. Common examples for the moment are
  * WebView, SurfaceView, AdView, etc.
  *
- * [AndroidView] will clip its content to the layout bounds, as being clipped is a common
- * assumption made by [View]s - keeping clipping disabled might lead to unexpected drawing behavior.
- * Note this deviates from Compose's practice of keeping clipping opt-in, disabled by default.
+ * [AndroidView] will not clip its content to the layout bounds. Use [View.setClipToOutline] on
+ * the child View to clip the contents, if desired. Developers will likely want to do this with
+ * all subclasses of SurfaceView to keep its contents contained.
  *
  * [AndroidView] has nested scroll interop capabilities if the containing view has nested scroll
  * enabled. This means this Composable can dispatch scroll deltas if it is placed inside a
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
index 7638528..4694d96 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
@@ -35,6 +35,8 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.compose.ui.input.canScroll
+import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.IntrinsicMeasureScope
 import androidx.compose.ui.layout.Measurable
@@ -181,6 +183,8 @@
     private val nestedScrollingParentHelper: NestedScrollingParentHelper =
         NestedScrollingParentHelper(this)
 
+    private var isInScrollContainer: () -> Boolean = { true }
+
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         view?.measure(widthMeasureSpec, heightMeasureSpec)
         setMeasuredDimension(view?.measuredWidth ?: 0, view?.measuredHeight ?: 0)
@@ -280,11 +284,15 @@
                     (layoutNode.owner as? AndroidComposeView)
                         ?.drawAndroidView(this@AndroidViewHolder, canvas.nativeCanvas)
                 }
-            }.onGloballyPositioned {
+            }
+            .onGloballyPositioned {
                 // The global position of this LayoutNode can change with it being replaced. For
                 // these cases, we need to inform the View.
                 layoutAccordingTo(layoutNode)
             }
+            .consumeScrollContainerInfo { scrollContainerInfo ->
+                isInScrollContainer = { scrollContainerInfo?.canScroll() == true }
+            }
         layoutNode.modifier = modifier.then(coreModifier)
         onModifierChanged = { layoutNode.modifier = it.then(coreModifier) }
 
@@ -398,9 +406,7 @@
         }
     }
 
-    // TODO: b/203141462 - consume whether the AndroidView() is inside a scrollable container, and
-    //  use that to set this. In the meantime set true as the defensive default.
-    override fun shouldDelayChildPressedState(): Boolean = true
+    override fun shouldDelayChildPressedState(): Boolean = isInScrollContainer()
 
     // NestedScrollingParent3
     override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
diff --git a/compose/ui/ui/src/androidMain/res/values-en-rCA/strings.xml b/compose/ui/ui/src/androidMain/res/values-en-rCA/strings.xml
index dbf19c5..9df0af4 100644
--- a/compose/ui/ui/src/androidMain/res/values-en-rCA/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-en-rCA/strings.xml
@@ -17,21 +17,21 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="indeterminate" msgid="7933458017204019916">"Partially ticked"</string>
+    <string name="indeterminate" msgid="7933458017204019916">"Partially checked"</string>
     <string name="on" msgid="8655164131929253426">"On"</string>
     <string name="off" msgid="875452955155264703">"Off"</string>
     <string name="switch_role" msgid="2561197295334830845">"Switch"</string>
     <string name="selected" msgid="6043586758067023">"Selected"</string>
     <string name="not_selected" msgid="6610465462668679431">"Not selected"</string>
-    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> per cent."</string>
+    <string name="template_percent" msgid="5946805113151406391">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> percent."</string>
     <string name="in_progress" msgid="6827826412747255547">"In progress"</string>
     <string name="tab" msgid="1672349317127674378">"Tab"</string>
     <string name="navigation_menu" msgid="542007171693138492">"Navigation menu"</string>
-    <string name="dropdown_menu" msgid="1890207353314751437">"Drop-down menu"</string>
+    <string name="dropdown_menu" msgid="1890207353314751437">"Dropdown menu"</string>
     <string name="close_drawer" msgid="406453423630273620">"Close navigation menu"</string>
     <string name="close_sheet" msgid="7573152094250666567">"Close sheet"</string>
     <string name="default_error_message" msgid="8038256446254964252">"Invalid input"</string>
-    <string name="default_popup_window_title" msgid="6312721426453364202">"Pop-up window"</string>
+    <string name="default_popup_window_title" msgid="6312721426453364202">"Pop-Up Window"</string>
     <string name="range_start" msgid="7097486360902471446">"Range start"</string>
     <string name="range_end" msgid="5941395253238309765">"Range end"</string>
 </resources>
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
index 81c1629a..5b0c82b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
@@ -210,8 +210,11 @@
         requireNotNull(focusModifier2)
 
         // Ignore focus modifiers that won't be considered during focus search.
-        if (!focusModifier1.isEligibleForFocusSearch) return 0
-        if (!focusModifier2.isEligibleForFocusSearch) return 0
+        if (!focusModifier1.isEligibleForFocusSearch || !focusModifier2.isEligibleForFocusSearch) {
+            if (focusModifier1.isEligibleForFocusSearch) return -1
+            if (focusModifier2.isEligibleForFocusSearch) return 1
+            return 0
+        }
 
         val layoutNode1 = checkNotNull(focusModifier1.coordinator?.layoutNode)
         val layoutNode2 = checkNotNull(focusModifier2.coordinator?.layoutNode)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/ScrollContainerInfo.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/ScrollContainerInfo.kt
new file mode 100644
index 0000000..2e8afd7
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/ScrollContainerInfo.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.input
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.modifier.ModifierLocalConsumer
+import androidx.compose.ui.modifier.ModifierLocalProvider
+import androidx.compose.ui.modifier.ModifierLocalReadScope
+import androidx.compose.ui.modifier.ProvidableModifierLocal
+import androidx.compose.ui.modifier.modifierLocalConsumer
+import androidx.compose.ui.modifier.modifierLocalOf
+import androidx.compose.ui.platform.debugInspectorInfo
+
+/**
+ * Represents a component that handles scroll events, so that other components in the hierarchy
+ * can adjust their behaviour.
+ * @See [provideScrollContainerInfo] and [consumeScrollContainerInfo]
+ */
+interface ScrollContainerInfo {
+    /** @return whether this component handles horizontal scroll events */
+    fun canScrollHorizontally(): Boolean
+    /** @return whether this component handles vertical scroll events */
+    fun canScrollVertically(): Boolean
+}
+
+/** @return whether this container handles either horizontal or vertical scroll events */
+fun ScrollContainerInfo.canScroll() = canScrollVertically() || canScrollHorizontally()
+
+/**
+ * A modifier to query whether there are any parents in the hierarchy that handle scroll events.
+ * The [ScrollContainerInfo] provided in [consumer] will recursively look for ancestors if the
+ * nearest parent does not handle scroll events in the queried direction.
+ * This can be used to delay UI changes in cases where a pointer event may later become a scroll,
+ * cancelling any existing press or other gesture.
+ *
+ * @sample androidx.compose.ui.samples.ScrollableContainerSample
+ */
+@OptIn(ExperimentalComposeUiApi::class)
+fun Modifier.consumeScrollContainerInfo(consumer: (ScrollContainerInfo?) -> Unit): Modifier =
+    modifierLocalConsumer {
+        consumer(ModifierLocalScrollContainerInfo.current)
+    }
+
+/**
+ * A Modifier that indicates that this component handles scroll events. Use
+ * [consumeScrollContainerInfo] to query whether there is a parent in the hierarchy that is
+ * a [ScrollContainerInfo].
+ */
+fun Modifier.provideScrollContainerInfo(scrollContainerInfo: ScrollContainerInfo): Modifier =
+    composed(
+        inspectorInfo = debugInspectorInfo {
+            name = "provideScrollContainerInfo"
+            value = scrollContainerInfo
+        }) {
+    remember(scrollContainerInfo) {
+        ScrollContainerInfoModifierLocal(scrollContainerInfo)
+    }
+}
+
+/**
+ * ModifierLocal to propagate ScrollableContainer throughout the hierarchy.
+ * This Modifier will recursively check for ancestor ScrollableContainers,
+ * if the current ScrollableContainer does not handle scroll events in a particular direction.
+ */
+private class ScrollContainerInfoModifierLocal(
+    private val scrollContainerInfo: ScrollContainerInfo,
+) : ScrollContainerInfo, ModifierLocalProvider<ScrollContainerInfo?>, ModifierLocalConsumer {
+
+    private var parent: ScrollContainerInfo? by mutableStateOf(null)
+
+    override val key: ProvidableModifierLocal<ScrollContainerInfo?> =
+        ModifierLocalScrollContainerInfo
+    override val value: ScrollContainerInfoModifierLocal = this
+
+    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) = with(scope) {
+        parent = ModifierLocalScrollContainerInfo.current
+    }
+
+    override fun canScrollHorizontally(): Boolean {
+        return scrollContainerInfo.canScrollHorizontally() ||
+            parent?.canScrollHorizontally() == true
+    }
+
+    override fun canScrollVertically(): Boolean {
+        return scrollContainerInfo.canScrollVertically() || parent?.canScrollVertically() == true
+    }
+}
+
+internal val ModifierLocalScrollContainerInfo = modifierLocalOf<ScrollContainerInfo?> {
+    null
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
index e4f5e82..4edb9c4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
@@ -38,7 +38,7 @@
  * The input data is provided by calling [addPosition]. Adding data is cheap.
  *
  * To obtain a velocity, call [calculateVelocity]. This will compute the velocity
- * based on the data added so far. Only call this when  you need to use the velocity,
+ * based on the data added so far. Only call this when you need to use the velocity,
  * as it is comparatively expensive.
  *
  * The quality of the velocity estimation will be better if more data points
@@ -52,9 +52,9 @@
     internal var currentPointerPositionAccumulator = Offset.Zero
 
     /**
-     * Adds a position as the given time to the tracker.
+     * Adds a position at the given time to the tracker.
      *
-     * Call resetTracking to remove added Offsets.
+     * Call [resetTracking] to remove added [Offset]s.
      *
      * @see resetTracking
      */
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt
index 7a9486c..4774c9f 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/RecordingInputConnectionUpdateTextFieldValueTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.input
 
-import android.view.View
 import android.view.inputmethod.ExtractedText
 import android.view.inputmethod.InputConnection
 import androidx.compose.ui.text.TextRange
@@ -25,6 +24,10 @@
 import androidx.compose.ui.text.input.RecordingInputConnection
 import androidx.compose.ui.text.input.TextFieldValue
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
@@ -32,10 +35,6 @@
 import org.mockito.kotlin.never
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
 class RecordingInputConnectionUpdateTextFieldValueTest {
@@ -56,48 +55,45 @@
     @Test
     fun test_update_input_state() {
         val imm: InputMethodManager = mock()
-        val view: View = mock()
 
         val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange.Zero)
 
-        ic.updateInputState(inputState, imm, view)
+        ic.updateInputState(inputState, imm)
 
-        verify(imm, times(1)).updateSelection(eq(view), eq(0), eq(0), eq(-1), eq(-1))
-        verify(imm, never()).updateExtractedText(any(), any(), any())
+        verify(imm, times(1)).updateSelection(eq(0), eq(0), eq(-1), eq(-1))
+        verify(imm, never()).updateExtractedText(any(), any())
     }
 
     @Test
     fun test_update_input_state_inactive() {
         val imm: InputMethodManager = mock()
-        val view: View = mock()
 
         val previousTextFieldValue = ic.mTextFieldValue
         ic.closeConnection()
 
         val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange.Zero)
-        ic.updateInputState(inputState, imm, view)
+        ic.updateInputState(inputState, imm)
 
         assertThat(ic.mTextFieldValue).isEqualTo(previousTextFieldValue)
-        verify(imm, never()).updateSelection(any(), any(), any(), any(), any())
-        verify(imm, never()).updateExtractedText(any(), any(), any())
+        verify(imm, never()).updateSelection(any(), any(), any(), any())
+        verify(imm, never()).updateExtractedText(any(), any())
     }
 
     @Test
     fun test_update_input_state_extracted_text_monitor() {
         val imm: InputMethodManager = mock()
-        val view: View = mock()
 
         ic.getExtractedText(null, InputConnection.GET_EXTRACTED_TEXT_MONITOR)
 
         val inputState = TextFieldValue(text = "Hello, World.", selection = TextRange.Zero)
 
-        ic.updateInputState(inputState, imm, view)
+        ic.updateInputState(inputState, imm)
 
-        verify(imm, times(1)).updateSelection(eq(view), eq(0), eq(0), eq(-1), eq(-1))
+        verify(imm, times(1)).updateSelection(eq(0), eq(0), eq(-1), eq(-1))
 
         val captor = argumentCaptor<ExtractedText>()
 
-        verify(imm, times(1)).updateExtractedText(any(), any(), captor.capture())
+        verify(imm, times(1)).updateExtractedText(any(), captor.capture())
 
         assertThat(captor.allValues.size).isEqualTo(1)
         assertThat(captor.firstValue.text).isEqualTo("Hello, World.")
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt
index 32854e2..9de574d 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt
@@ -16,12 +16,9 @@
 
 package androidx.compose.ui.text.input
 
-import android.os.IBinder
 import android.view.View
 import android.view.inputmethod.ExtractedText
 import com.google.common.truth.Truth.assertThat
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancel
@@ -32,6 +29,8 @@
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class TextInputServiceAndroidCommandDebouncingTest {
@@ -59,9 +58,9 @@
         service.showSoftwareKeyboard()
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.showSoftInputCalls).hasSize(1)
-        assertThat(inputMethodManager.restartCalls).isEmpty()
-        assertThat(inputMethodManager.hideSoftInputCalls).isEmpty()
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(1)
+        assertThat(inputMethodManager.restartCalls).isEqualTo(0)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(0)
     }
 
     @Test
@@ -69,9 +68,9 @@
         service.hideSoftwareKeyboard()
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.hideSoftInputCalls).hasSize(1)
-        assertThat(inputMethodManager.restartCalls).isEmpty()
-        assertThat(inputMethodManager.showSoftInputCalls).isEmpty()
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(1)
+        assertThat(inputMethodManager.restartCalls).isEqualTo(0)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
     }
 
     @Test
@@ -79,7 +78,7 @@
         service.startInput()
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.restartCalls).hasSize(1)
+        assertThat(inputMethodManager.restartCalls).isEqualTo(1)
     }
 
     @Test
@@ -87,7 +86,7 @@
         service.startInput()
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.showSoftInputCalls).hasSize(1)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(1)
     }
 
     @Test
@@ -95,7 +94,7 @@
         service.stopInput()
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.restartCalls).hasSize(1)
+        assertThat(inputMethodManager.restartCalls).isEqualTo(1)
     }
 
     @Test
@@ -103,7 +102,7 @@
         service.stopInput()
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.hideSoftInputCalls).hasSize(1)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(1)
     }
 
     @Test
@@ -116,7 +115,7 @@
         // in either order, should debounce to a single restart call. If they aren't de-duped, the
         // keyboard may flicker if one of the calls configures the IME in a non-default way (e.g.
         // number input).
-        assertThat(inputMethodManager.restartCalls).hasSize(1)
+        assertThat(inputMethodManager.restartCalls).isEqualTo(1)
     }
 
     @Test
@@ -129,7 +128,7 @@
         // in either order, should debounce to a single restart call. If they aren't de-duped, the
         // keyboard may flicker if one of the calls configures the IME in a non-default way (e.g.
         // number input).
-        assertThat(inputMethodManager.restartCalls).hasSize(1)
+        assertThat(inputMethodManager.restartCalls).isEqualTo(1)
     }
 
     @Test
@@ -140,7 +139,7 @@
 
         // After stopInput, there's no input connection, so any calls to show the keyboard should
         // be ignored until the next call to startInput.
-        assertThat(inputMethodManager.showSoftInputCalls).isEmpty()
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
     }
 
     @Test
@@ -152,7 +151,7 @@
         // stopInput will hide the keyboard implicitly, so both stopInput and hideSoftwareKeyboard
         // have the effect "hide the keyboard". These two effects should be debounced and the IMM
         // should only get a single hide call instead of two redundant calls.
-        assertThat(inputMethodManager.hideSoftInputCalls).hasSize(1)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(1)
     }
 
     @Test
@@ -162,7 +161,7 @@
         }
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.showSoftInputCalls).hasSize(1)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(1)
     }
 
     @Test
@@ -172,7 +171,7 @@
         }
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.hideSoftInputCalls).hasSize(1)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(1)
     }
 
     @Test
@@ -181,8 +180,8 @@
         service.hideSoftwareKeyboard()
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.showSoftInputCalls).hasSize(0)
-        assertThat(inputMethodManager.hideSoftInputCalls).hasSize(1)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(1)
     }
 
     @Test
@@ -191,40 +190,40 @@
         service.showSoftwareKeyboard()
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.showSoftInputCalls).hasSize(1)
-        assertThat(inputMethodManager.hideSoftInputCalls).hasSize(0)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(1)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(0)
     }
 
     @Test fun stopInput_isNotProcessedImmediately() {
         service.stopInput()
 
-        assertThat(inputMethodManager.restartCalls).isEmpty()
-        assertThat(inputMethodManager.showSoftInputCalls).isEmpty()
-        assertThat(inputMethodManager.hideSoftInputCalls).isEmpty()
+        assertThat(inputMethodManager.restartCalls).isEqualTo(0)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(0)
     }
 
     @Test fun startInput_isNotProcessedImmediately() {
         service.startInput()
 
-        assertThat(inputMethodManager.restartCalls).isEmpty()
-        assertThat(inputMethodManager.showSoftInputCalls).isEmpty()
-        assertThat(inputMethodManager.hideSoftInputCalls).isEmpty()
+        assertThat(inputMethodManager.restartCalls).isEqualTo(0)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(0)
     }
 
     @Test fun showSoftwareKeyboard_isNotProcessedImmediately() {
         service.showSoftwareKeyboard()
 
-        assertThat(inputMethodManager.restartCalls).isEmpty()
-        assertThat(inputMethodManager.showSoftInputCalls).isEmpty()
-        assertThat(inputMethodManager.hideSoftInputCalls).isEmpty()
+        assertThat(inputMethodManager.restartCalls).isEqualTo(0)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(0)
     }
 
     @Test fun hideSoftwareKeyboard_isNotProcessedImmediately() {
         service.hideSoftwareKeyboard()
 
-        assertThat(inputMethodManager.restartCalls).isEmpty()
-        assertThat(inputMethodManager.showSoftInputCalls).isEmpty()
-        assertThat(inputMethodManager.hideSoftInputCalls).isEmpty()
+        assertThat(inputMethodManager.restartCalls).isEqualTo(0)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(0)
     }
 
     @Test fun commandsAreIgnored_ifFocusLostBeforeProcessing() {
@@ -235,7 +234,7 @@
         // Process the queued commands.
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.showSoftInputCalls).isEmpty()
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
     }
 
     @Test fun commandsAreDrained_whenProcessedWithoutFocus() {
@@ -246,7 +245,7 @@
         whenever(view.isFocused).thenReturn(true)
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.showSoftInputCalls).isEmpty()
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
     }
 
     private fun TextInputServiceAndroid.startInput() {
@@ -259,27 +258,26 @@
     }
 
     private class TestInputMethodManager : InputMethodManager {
-        val restartCalls = mutableListOf<View>()
-        val showSoftInputCalls = mutableListOf<View>()
-        val hideSoftInputCalls = mutableListOf<IBinder?>()
+        var restartCalls = 0
+        var showSoftInputCalls = 0
+        var hideSoftInputCalls = 0
 
-        override fun restartInput(view: View) {
-            restartCalls += view
+        override fun restartInput() {
+            restartCalls++
         }
 
-        override fun showSoftInput(view: View) {
-            showSoftInputCalls += view
+        override fun showSoftInput() {
+            showSoftInputCalls++
         }
 
-        override fun hideSoftInputFromWindow(windowToken: IBinder?) {
-            hideSoftInputCalls += windowToken
+        override fun hideSoftInput() {
+            hideSoftInputCalls++
         }
 
-        override fun updateExtractedText(view: View, token: Int, extractedText: ExtractedText) {
+        override fun updateExtractedText(token: Int, extractedText: ExtractedText) {
         }
 
         override fun updateSelection(
-            view: View,
             selectionStart: Int,
             selectionEnd: Int,
             compositionStart: Int,
diff --git a/constraintlayout/constraintlayout-compose/api/restricted_current.txt b/constraintlayout/constraintlayout-compose/api/restricted_current.txt
index d11ace4..d13a833 100644
--- a/constraintlayout/constraintlayout-compose/api/restricted_current.txt
+++ b/constraintlayout/constraintlayout-compose/api/restricted_current.txt
@@ -539,6 +539,7 @@
     method public void setDebugMode(androidx.constraintlayout.compose.MotionLayoutDebugFlags motionDebugFlag);
     method public void snapTo(float newProgress);
     property public float currentProgress;
+    property @kotlin.PublishedApi internal final androidx.constraintlayout.compose.MotionLayoutDebugFlags debugMode;
     property public boolean isInDebugMode;
     field @kotlin.PublishedApi internal final androidx.constraintlayout.compose.MotionProgress motionProgress;
   }
diff --git a/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewWithCollapsingToolbarTest.java b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewWithCollapsingToolbarTest.java
new file mode 100644
index 0000000..47133e0
--- /dev/null
+++ b/core/core/src/androidTest/java/androidx/core/widget/NestedScrollViewWithCollapsingToolbarTest.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.core.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that CollapsingToolbarLayout properly collapses/expands with a NestedScrollView.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NestedScrollViewWithCollapsingToolbarTest {
+    private static final String LONG_TEXT = "This is some long text. It just keeps going. Look at"
+            + " it. Scroll it. Scroll the nested version of it. This is some long text. It just"
+            + " keeps going. Look at it. Scroll it. Scroll the nested version of it. This is some"
+            + " long text. It just keeps going. Look at it. Scroll it. Scroll the nested version of"
+            + " it. This is some long text. It just keeps going. Look at it. Scroll it. Scroll the"
+            + " nested version of it. This is some long text. It just keeps going. Look at it."
+            + " Scroll it. Scroll the nested version of it. This is some long text. It just keeps"
+            + " going. Look at it. Scroll it. Scroll the nested version of it. This is some long"
+            + " text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it."
+            + " This is some long text. It just keeps going. Look at it. Scroll it. Scroll the"
+            + " nested version of it. This is some long text. It just keeps going. Look at it."
+            + " Scroll it. Scroll the nested version of it. This is some long text. It just keeps"
+            + " going. Look at it. Scroll it. Scroll the nested version of it. This is some long"
+            + " text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it."
+            + " This is some long text. It just keeps going. Look at it. Scroll it. Scroll the"
+            + " nested version of it. This is some long text. It just keeps going. Look at it."
+            + " Scroll it. Scroll the nested version of it. This is some long text. It just keeps"
+            + " going. Look at it. Scroll it. Scroll the nested version of it. This is some long"
+            + " text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it."
+            + " This is some long text. It just keeps going. Look at it. Scroll it. Scroll the"
+            + " nested version of it. This is some long text. It just keeps going. Look at it."
+            + " Scroll it. Scroll the nested version of it. This is some long text. It just keeps"
+            + " going. Look at it. Scroll it. Scroll the nested version of it. This is some long"
+            + " text. It just keeps going. Look at it. Scroll it. Scroll the nested version of it."
+            + " This is some long text. It just keeps going. Look at it. Scroll it. Scroll the"
+            + " nested version of it.";
+
+    private MockCoordinatorLayoutWithCollapsingToolbarAndNestedScrollView mParentNestedScrollView;
+
+    private MockCoordinatorLayoutWithCollapsingToolbarAndNestedScrollView mChildNestedScrollView;
+
+    @Test
+    public void isOnStartNestedScrollTriggered_touchSwipeUpInChild_triggeredInParent() {
+        // Arrange
+        setupNestedScrollViewInNestedScrollView(
+                ApplicationProvider.getApplicationContext(),
+                100,
+                600);
+
+        // Act
+        // Swipes from the bottom of the child to the top of child.
+        swipeUp(mChildNestedScrollView, false);
+
+        // Assert
+        // Should trigger scroll event(s) in parent (touch may trigger more than one).
+        assertTrue(mParentNestedScrollView.getOnStartNestedScrollCount() > 0);
+        // Should not trigger in child (because child doesn't have its own inner NestedScrollView).
+        assertEquals(0, mChildNestedScrollView.getOnStartNestedScrollCount());
+    }
+
+    @Test
+    public void isOnStartNestedScrollTriggered_touchSwipeDownInChild_triggeredInParent() {
+        // Arrange
+        setupNestedScrollViewInNestedScrollView(
+                ApplicationProvider.getApplicationContext(),
+                100,
+                600);
+
+        // Act
+        // Swipes from the top of the child to the bottom of child
+        swipeDown(mChildNestedScrollView, false);
+
+        // Assert
+        // Should trigger scroll event(s) in parent (touch may trigger more than one).
+        assertTrue(mParentNestedScrollView.getOnStartNestedScrollCount() > 0);
+        // Should not trigger in child (because child doesn't have its own inner NestedScrollView).
+        assertEquals(0, mChildNestedScrollView.getOnStartNestedScrollCount());
+    }
+
+
+    @Test
+    public void isOnStartNestedScrollTriggered_rotaryScrollInChildPastTop_triggeredInParent() {
+        // Arrange
+        setupNestedScrollViewInNestedScrollView(
+                ApplicationProvider.getApplicationContext(),
+                100,
+                600);
+
+        // Act
+        sendScroll(
+                mChildNestedScrollView,
+                2f,
+                InputDevice.SOURCE_ROTARY_ENCODER
+        );
+
+        // Assert
+        // Should trigger in parent of scroll event.
+        assertEquals(1, mParentNestedScrollView.getOnStartNestedScrollCount());
+        // Should not trigger in child (because child doesn't have its own inner NestedScrollView).
+        assertEquals(0, mChildNestedScrollView.getOnStartNestedScrollCount());
+    }
+
+    @Test
+    public void isOnStartNestedScrollTriggered_rotaryScrollInChildPastBottom_triggeredInParent() {
+        // Arrange
+        setupNestedScrollViewInNestedScrollView(
+                ApplicationProvider.getApplicationContext(),
+                100,
+                600);
+        // Move to bottom of the child NestedScrollView, so we can try scrolling past it.
+        int scrollRange = mChildNestedScrollView.getScrollRange();
+        mChildNestedScrollView.scrollTo(0, scrollRange);
+
+        // Act
+        sendScroll(
+                mChildNestedScrollView,
+                -2f,
+                InputDevice.SOURCE_ROTARY_ENCODER
+        );
+
+        // Assert
+        // Should trigger in parent of scroll event.
+        assertEquals(1, mParentNestedScrollView.getOnStartNestedScrollCount());
+        // Should not trigger in child (because child doesn't have its own inner NestedScrollView).
+        assertEquals(0, mChildNestedScrollView.getOnStartNestedScrollCount());
+    }
+
+    @Test
+    public void isOnStartNestedScrollTriggered_mouseScrollInChildPastTop_triggeredInParent() {
+        // Arrange
+        setupNestedScrollViewInNestedScrollView(
+                ApplicationProvider.getApplicationContext(),
+                100,
+                600);
+
+        // Act
+        sendScroll(
+                mChildNestedScrollView,
+                2f,
+                InputDevice.SOURCE_MOUSE
+        );
+
+        // Assert
+        // Should trigger in parent of scroll event.
+        assertEquals(1, mParentNestedScrollView.getOnStartNestedScrollCount());
+        // Should not trigger in child (because child doesn't have its own inner NestedScrollView).
+        assertEquals(0, mChildNestedScrollView.getOnStartNestedScrollCount());
+    }
+
+    @Test
+    public void isOnStartNestedScrollTriggered_mouseScrollInChildPastBottom_triggeredInParent() {
+        // Arrange
+        setupNestedScrollViewInNestedScrollView(
+                ApplicationProvider.getApplicationContext(),
+                100,
+                600);
+        // Move to bottom of the child NestedScrollView, so we can try scrolling past it.
+        int scrollRange = mChildNestedScrollView.getScrollRange();
+        mChildNestedScrollView.scrollTo(0, scrollRange);
+
+        // Act
+        sendScroll(
+                mChildNestedScrollView,
+                -2f,
+                InputDevice.SOURCE_MOUSE
+        );
+
+        // Assert
+        // Should trigger in parent of scroll event.
+        assertEquals(1, mParentNestedScrollView.getOnStartNestedScrollCount());
+        // Should not trigger in child (because child doesn't have its own inner NestedScrollView).
+        assertEquals(0, mChildNestedScrollView.getOnStartNestedScrollCount());
+    }
+
+
+    private TextView createTextView(Context context, int width, int height, String textContent) {
+        TextView textView = new TextView(context);
+        textView.setMinimumWidth(width);
+        textView.setMinimumHeight(height);
+        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT,
+                LinearLayout.LayoutParams.WRAP_CONTENT
+        );
+        textView.setLayoutParams(layoutParams);
+        textView.setText(textContent);
+
+        return textView;
+    }
+
+    private void setupNestedScrollViewInNestedScrollView(Context context, int width, int height) {
+        // The parent NestedScrollView contains a LinearLayout with three Views:
+        //  1. TextView
+        //  2. A child NestedScrollView (contains its own TextView)
+        //  3. TextView
+        int childHeight = height / 3;
+
+        // Creates child NestedScrollView first
+        mChildNestedScrollView =
+                new MockCoordinatorLayoutWithCollapsingToolbarAndNestedScrollView(context);
+        mChildNestedScrollView.setMinimumWidth(width);
+        mChildNestedScrollView.setMinimumHeight(childHeight);
+        NestedScrollView.LayoutParams nestedChildLayoutParams = new NestedScrollView.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                childHeight
+        );
+        mChildNestedScrollView.setLayoutParams(nestedChildLayoutParams);
+
+        mChildNestedScrollView.setBackgroundColor(0xFF0000FF);
+        mChildNestedScrollView.addView(createTextView(context, width, childHeight, LONG_TEXT));
+
+
+        // Creates LinearLayout containing three Views (TextViews and previously created child
+        // NestedScrollView.
+        LinearLayout linearLayout = new LinearLayout(context);
+        linearLayout.setOrientation(LinearLayout.VERTICAL);
+        linearLayout.setMinimumWidth(width);
+        linearLayout.setMinimumHeight(height);
+        LinearLayout.LayoutParams linearLayoutParams = new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT,
+                LinearLayout.LayoutParams.WRAP_CONTENT
+        );
+        linearLayout.setLayoutParams(linearLayoutParams);
+
+        linearLayout.addView(createTextView(context, width, childHeight, LONG_TEXT));
+        linearLayout.addView(mChildNestedScrollView);
+        linearLayout.addView(createTextView(context, width, childHeight, LONG_TEXT));
+
+        mParentNestedScrollView =
+                new MockCoordinatorLayoutWithCollapsingToolbarAndNestedScrollView(context);
+        mParentNestedScrollView.setMinimumWidth(width);
+        mParentNestedScrollView.setMinimumHeight(height);
+        mParentNestedScrollView.setBackgroundColor(0xCC00FF00);
+        mParentNestedScrollView.addView(linearLayout);
+    }
+
+    private void swipeDown(View view, boolean shortSwipe) {
+        float endY = shortSwipe ? view.getHeight() / 2f : view.getHeight() - 1;
+        swipe(0, endY, view);
+    }
+
+    private void swipeUp(View view, boolean shortSwipe) {
+        float endY = shortSwipe ? view.getHeight() / 2f : 0;
+        swipe(view.getHeight() - 1, endY, view);
+    }
+
+    private void swipe(float startY, float endY, View view) {
+        float x = view.getWidth() / 2f;
+
+        MotionEvent down = MotionEvent.obtain(
+                0,
+                0,
+                MotionEvent.ACTION_DOWN,
+                x,
+                startY,
+                0
+        );
+        view.dispatchTouchEvent(down);
+
+        MotionEvent move = MotionEvent.obtain(
+                0,
+                10,
+                MotionEvent.ACTION_MOVE,
+                x,
+                endY,
+                0
+        );
+        view.dispatchTouchEvent(move);
+
+        MotionEvent up = MotionEvent.obtain(0,
+                1000,
+                MotionEvent.ACTION_UP,
+                x,
+                endY,
+                0
+        );
+        view.dispatchTouchEvent(up);
+    }
+
+    private void sendScroll(View view, float scrollAmount, int source) {
+        float x = view.getWidth() / 2f;
+        float y = view.getHeight() / 2f;
+        MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
+        pointerProperties.toolType = MotionEvent.TOOL_TYPE_MOUSE;
+        MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
+        pointerCoords.x = x;
+        pointerCoords.y = y;
+        int axis = source == InputDevice.SOURCE_ROTARY_ENCODER ? MotionEvent.AXIS_SCROLL
+                : MotionEvent.AXIS_VSCROLL;
+        pointerCoords.setAxisValue(axis, scrollAmount);
+
+        MotionEvent scroll = MotionEvent.obtain(
+                0, /* downTime */
+                0, /* eventTime */
+                MotionEvent.ACTION_SCROLL, /* action */
+                1, /* pointerCount */
+                new MotionEvent.PointerProperties[] { pointerProperties },
+                new MotionEvent.PointerCoords[] { pointerCoords },
+                0, /* metaState */
+                0, /* buttonState */
+                0f, /* xPrecision */
+                0f, /* yPrecision */
+                0, /* deviceId */
+                0, /* edgeFlags */
+                source, /* source */
+                0 /* flags */
+        );
+
+        view.dispatchGenericMotionEvent(scroll);
+    }
+
+    /*
+     * Since CollapsingToolbarLayout relies on NestedScrollView.onStartNestedScroll() being
+     * triggered
+     * to collapse/expand itself, we can just test when that method is triggered (and count that) to
+     * cover testing CollapsingToolbarLayout collapsing/expanding.
+     */
+    class MockCoordinatorLayoutWithCollapsingToolbarAndNestedScrollView extends NestedScrollView {
+        private int mOnStartNestedScrollCount = 0;
+        public int getOnStartNestedScrollCount() {
+            return mOnStartNestedScrollCount;
+        }
+
+        MockCoordinatorLayoutWithCollapsingToolbarAndNestedScrollView(Context context) {
+            super(context);
+        }
+
+        MockCoordinatorLayoutWithCollapsingToolbarAndNestedScrollView(
+                Context context,
+                AttributeSet attrs
+        ) {
+            super(context, attrs);
+        }
+
+        MockCoordinatorLayoutWithCollapsingToolbarAndNestedScrollView(
+                Context context,
+                AttributeSet attrs,
+                int defStyleAttr
+        ) {
+            super(context, attrs, defStyleAttr);
+        }
+
+        @Override
+        public boolean onStartNestedScroll(
+                @NonNull View child,
+                @NonNull View target,
+                int axes,
+                int type
+        ) {
+            mOnStartNestedScrollCount++;
+            return super.onStartNestedScroll(child, target, axes, type);
+        }
+    }
+}
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 ff5861e..24b6a4b 100644
--- a/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
+++ b/core/core/src/main/java/androidx/core/widget/NestedScrollView.java
@@ -137,7 +137,8 @@
     public EdgeEffect mEdgeGlowBottom;
 
     /**
-     * Position of the last motion event.
+     * Position of the last motion event; only used with touch related events (usually to assist
+     * in movement changes in a drag gesture).
      */
     private int mLastMotionY;
 
@@ -189,10 +190,19 @@
     private int mActivePointerId = INVALID_POINTER;
 
     /**
-     * Used during scrolling to retrieve the new offset within the window.
+     * Used during scrolling to retrieve the new offset within the window. Saves memory by saving
+     * x, y changes to this array (0 position = x, 1 position = y) vs. reallocating an x and y
+     * every time.
      */
     private final int[] mScrollOffset = new int[2];
+
+    /*
+     * Used during scrolling to retrieve the new consumed offset within the window.
+     * Uses same memory saving strategy as mScrollOffset.
+     */
     private final int[] mScrollConsumed = new int[2];
+
+    // Used to track the position of the touch only events relative to the container.
     private int mNestedYOffset;
 
     private int mLastScrollerY;
@@ -290,8 +300,13 @@
     }
 
     @Override
-    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
-            @Nullable int[] offsetInWindow, int type) {
+    public boolean dispatchNestedPreScroll(
+            int dx,
+            int dy,
+            @Nullable int[] consumed,
+            @Nullable int[] offsetInWindow,
+            int type
+    ) {
         return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
     }
 
@@ -867,23 +882,26 @@
     }
 
     @Override
-    public boolean onTouchEvent(@NonNull MotionEvent ev) {
+    public boolean onTouchEvent(@NonNull MotionEvent motionEvent) {
         initVelocityTrackerIfNotExists();
 
-        final int actionMasked = ev.getActionMasked();
+        final int actionMasked = motionEvent.getActionMasked();
 
         if (actionMasked == MotionEvent.ACTION_DOWN) {
             mNestedYOffset = 0;
         }
 
-        MotionEvent vtev = MotionEvent.obtain(ev);
-        vtev.offsetLocation(0, mNestedYOffset);
+        MotionEvent velocityTrackerMotionEvent = MotionEvent.obtain(motionEvent);
+        velocityTrackerMotionEvent.offsetLocation(0, mNestedYOffset);
 
         switch (actionMasked) {
             case MotionEvent.ACTION_DOWN: {
                 if (getChildCount() == 0) {
                     return false;
                 }
+
+                // If additional fingers touch the screen while a drag is in progress, this block
+                // of code will make sure the drag isn't interrupted.
                 if (mIsBeingDragged) {
                     final ViewParent parent = getParent();
                     if (parent != null) {
@@ -899,22 +917,27 @@
                     abortAnimatedScroll();
                 }
 
-                // Remember where the motion event started
-                mLastMotionY = (int) ev.getY();
-                mActivePointerId = ev.getPointerId(0);
-                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
+                initializeTouchDrag(
+                        (int) motionEvent.getY(),
+                        motionEvent.getPointerId(0)
+                );
+
                 break;
             }
-            case MotionEvent.ACTION_MOVE:
-                final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+
+            case MotionEvent.ACTION_MOVE: {
+                final int activePointerIndex = motionEvent.findPointerIndex(mActivePointerId);
                 if (activePointerIndex == -1) {
                     Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
                     break;
                 }
 
-                final int y = (int) ev.getY(activePointerIndex);
+                final int y = (int) motionEvent.getY(activePointerIndex);
                 int deltaY = mLastMotionY - y;
-                deltaY -= releaseVerticalGlow(deltaY, ev.getX(activePointerIndex));
+                deltaY -= releaseVerticalGlow(deltaY, motionEvent.getX(activePointerIndex));
+
+                // Changes to dragged state if delta is greater than the slop (and not in
+                // the dragged state).
                 if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
                     final ViewParent parent = getParent();
                     if (parent != null) {
@@ -927,70 +950,18 @@
                         deltaY += mTouchSlop;
                     }
                 }
+
                 if (mIsBeingDragged) {
-                    // Start with nested pre scrolling
-                    if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset,
-                            ViewCompat.TYPE_TOUCH)) {
-                        deltaY -= mScrollConsumed[1];
-                        mNestedYOffset += mScrollOffset[1];
-                    }
-
-                    // Scroll to follow the motion event
-                    mLastMotionY = y - mScrollOffset[1];
-
-                    final int oldY = getScrollY();
-                    final int range = getScrollRange();
-                    final int overscrollMode = getOverScrollMode();
-                    boolean canOverscroll = overscrollMode == View.OVER_SCROLL_ALWAYS
-                            || (overscrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
-
-                    // Calling overScrollByCompat will call onOverScrolled, which
-                    // calls onScrollChanged if applicable.
-                    boolean clearVelocityTracker =
-                            overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0,
-                                    0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH);
-
-                    final int scrolledDeltaY = getScrollY() - oldY;
-                    final int unconsumedY = deltaY - scrolledDeltaY;
-
-                    mScrollConsumed[1] = 0;
-
-                    dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,
-                            ViewCompat.TYPE_TOUCH, mScrollConsumed);
-
-                    mLastMotionY -= mScrollOffset[1];
-                    mNestedYOffset += mScrollOffset[1];
-
-                    if (canOverscroll) {
-                        deltaY -= mScrollConsumed[1];
-                        final int pulledToY = oldY + deltaY;
-                        if (pulledToY < 0) {
-                            EdgeEffectCompat.onPullDistance(mEdgeGlowTop,
-                                    (float) -deltaY / getHeight(),
-                                    ev.getX(activePointerIndex) / getWidth());
-                            if (!mEdgeGlowBottom.isFinished()) {
-                                mEdgeGlowBottom.onRelease();
-                            }
-                        } else if (pulledToY > range) {
-                            EdgeEffectCompat.onPullDistance(mEdgeGlowBottom,
-                                    (float) deltaY / getHeight(),
-                                    1.f - ev.getX(activePointerIndex) / getWidth());
-                            if (!mEdgeGlowTop.isFinished()) {
-                                mEdgeGlowTop.onRelease();
-                            }
-                        }
-                        if (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished()) {
-                            ViewCompat.postInvalidateOnAnimation(this);
-                            clearVelocityTracker = false;
-                        }
-                    }
-                    if (clearVelocityTracker) {
-                        // Break our velocity if we hit a scroll barrier.
-                        mVelocityTracker.clear();
-                    }
+                    final int x = (int) motionEvent.getX(activePointerIndex);
+                    int scrollOffset = scrollBy(deltaY, x, ViewCompat.TYPE_TOUCH, false);
+                    // Updates the global positions (used by later move events to properly scroll).
+                    mLastMotionY = y - scrollOffset;
+                    mNestedYOffset += scrollOffset;
                 }
                 break;
-            case MotionEvent.ACTION_UP:
+            }
+
+            case MotionEvent.ACTION_UP: {
                 final VelocityTracker velocityTracker = mVelocityTracker;
                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
@@ -1004,39 +975,204 @@
                         getScrollRange())) {
                     ViewCompat.postInvalidateOnAnimation(this);
                 }
-                mActivePointerId = INVALID_POINTER;
-                endDrag();
+                endTouchDrag();
                 break;
-            case MotionEvent.ACTION_CANCEL:
+            }
+
+            case MotionEvent.ACTION_CANCEL: {
                 if (mIsBeingDragged && getChildCount() > 0) {
                     if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
                             getScrollRange())) {
                         ViewCompat.postInvalidateOnAnimation(this);
                     }
                 }
-                mActivePointerId = INVALID_POINTER;
-                endDrag();
-                break;
-            case MotionEvent.ACTION_POINTER_DOWN: {
-                final int index = ev.getActionIndex();
-                mLastMotionY = (int) ev.getY(index);
-                mActivePointerId = ev.getPointerId(index);
+                endTouchDrag();
                 break;
             }
-            case MotionEvent.ACTION_POINTER_UP:
-                onSecondaryPointerUp(ev);
-                mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
+
+            case MotionEvent.ACTION_POINTER_DOWN: {
+                final int index = motionEvent.getActionIndex();
+                mLastMotionY = (int) motionEvent.getY(index);
+                mActivePointerId = motionEvent.getPointerId(index);
                 break;
+            }
+
+            case MotionEvent.ACTION_POINTER_UP: {
+                onSecondaryPointerUp(motionEvent);
+                mLastMotionY =
+                        (int) motionEvent.getY(motionEvent.findPointerIndex(mActivePointerId));
+                break;
+            }
         }
 
         if (mVelocityTracker != null) {
-            mVelocityTracker.addMovement(vtev);
+            mVelocityTracker.addMovement(velocityTrackerMotionEvent);
         }
-        vtev.recycle();
+        // Returns object back to be re-used by others.
+        velocityTrackerMotionEvent.recycle();
 
         return true;
     }
 
+    private void initializeTouchDrag(int lastMotionY, int activePointerId) {
+        mLastMotionY = lastMotionY;
+        mActivePointerId = activePointerId;
+        startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
+    }
+
+    // Ends drag in a nested scroll.
+    private void endTouchDrag() {
+        mActivePointerId = INVALID_POINTER;
+        mIsBeingDragged = false;
+
+        recycleVelocityTracker();
+        stopNestedScroll(ViewCompat.TYPE_TOUCH);
+
+        mEdgeGlowTop.onRelease();
+        mEdgeGlowBottom.onRelease();
+    }
+
+    /*
+     * Handles scroll events for both touch and non-touch events (mouse scroll wheel,
+     * rotary button, etc.).
+     *
+     * Note: This returns the total scroll offset for touch event which is required for calculating
+     * the scroll between move events. This returned value is NOT needed for non-touch events since
+     * a scroll is a one time event.
+     */
+    private int scrollBy(
+            int verticalScrollDistance,
+            int x,
+            int touchType,
+            boolean isSourceMouse
+    ) {
+        int totalScrollOffset = 0;
+
+        /*
+         * Starts nested scrolling for non-touch events (mouse scroll wheel, rotary button, etc.).
+         * This is in contrast to a touch event which would trigger the start of nested scrolling
+         * with a touch down event outside of this method, since for a single gesture scrollBy()
+         * might be called several times for a move event for a single drag gesture.
+         */
+        if (touchType == ViewCompat.TYPE_NON_TOUCH) {
+            startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, touchType);
+        }
+
+        // Dispatches scrolling delta amount available to parent (to consume what it needs).
+        // Note: The amounts the parent consumes are saved in arrays named mScrollConsumed and
+        // mScrollConsumed to save space.
+        if (dispatchNestedPreScroll(
+                0,
+                verticalScrollDistance,
+                mScrollConsumed,
+                mScrollOffset,
+                touchType)
+        ) {
+            // Deducts the scroll amount (y) consumed by the parent (x in position 0,
+            // y in position 1). Nested scroll only works with Y position (so we don't use x).
+            verticalScrollDistance -= mScrollConsumed[1];
+            totalScrollOffset += mScrollOffset[1];
+        }
+
+        // Retrieves the scroll y position (top position of this view) and scroll Y range (how far
+        // the scroll can go).
+        final int initialScrollY = getScrollY();
+        final int scrollRangeY = getScrollRange();
+
+        // Overscroll is for adding animations at the top/bottom of a view when the user scrolls
+        // beyond the beginning/end of the view. Overscroll is not used with a mouse.
+        boolean canOverscroll = canOverScroll() && !isSourceMouse;
+
+        // Scrolls content in the current View, but clamps it if it goes too far.
+        boolean hitScrollBarrier =
+                overScrollByCompat(
+                        0,
+                        verticalScrollDistance,
+                        0,
+                        initialScrollY,
+                        0,
+                        scrollRangeY,
+                        0,
+                        0,
+                        true
+                ) && !hasNestedScrollingParent(touchType);
+
+        // The position may have been adjusted in the previous call, so we must revise our values.
+        final int scrollYDelta = getScrollY() - initialScrollY;
+        final int unconsumedY = verticalScrollDistance - scrollYDelta;
+
+        // Reset the Y consumed scroll to zero
+        mScrollConsumed[1] = 0;
+
+        //  Dispatch the unconsumed delta Y to the children to consume.
+        dispatchNestedScroll(
+                0,
+                scrollYDelta,
+                0,
+                unconsumedY,
+                mScrollOffset,
+                touchType,
+                mScrollConsumed
+        );
+
+        totalScrollOffset += mScrollOffset[1];
+
+        // Handle overscroll of the children.
+        verticalScrollDistance -= mScrollConsumed[1];
+        int newScrollY = initialScrollY + verticalScrollDistance;
+
+        if (newScrollY < 0) {
+            if (canOverscroll) {
+                EdgeEffectCompat.onPullDistance(
+                        mEdgeGlowTop,
+                        (float) -verticalScrollDistance / getHeight(),
+                        (float) x / getWidth()
+                );
+
+                if (!mEdgeGlowBottom.isFinished()) {
+                    mEdgeGlowBottom.onRelease();
+                }
+            }
+
+        } else if (newScrollY > scrollRangeY) {
+            if (canOverscroll) {
+                EdgeEffectCompat.onPullDistance(
+                        mEdgeGlowBottom,
+                        (float) verticalScrollDistance / getHeight(),
+                        1.f - ((float) x / getWidth())
+                );
+
+                if (!mEdgeGlowTop.isFinished()) {
+                    mEdgeGlowTop.onRelease();
+                }
+            }
+        }
+
+        if (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished()) {
+            ViewCompat.postInvalidateOnAnimation(this);
+            hitScrollBarrier = false;
+        }
+
+        if (hitScrollBarrier && (touchType == ViewCompat.TYPE_TOUCH)) {
+            // Break our velocity if we hit a scroll barrier.
+            mVelocityTracker.clear();
+        }
+
+        /*
+         * Ends nested scrolling for non-touch events (mouse scroll wheel, rotary button, etc.).
+         * As noted above, this is in contrast to a touch event.
+         */
+        if (touchType == ViewCompat.TYPE_NON_TOUCH) {
+            stopNestedScroll(touchType);
+
+            // Required for scrolling with Rotary Device stretch top/bottom to work properly
+            mEdgeGlowTop.onRelease();
+            mEdgeGlowBottom.onRelease();
+        }
+
+        return totalScrollOffset;
+    }
+
     /**
      * Returns true if edgeEffect should call onAbsorb() with veclocity or false if it should
      * animate with a fling. It will animate with a fling if the velocity will remove the
@@ -1164,54 +1300,36 @@
     }
 
     @Override
-    public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_SCROLL && !mIsBeingDragged) {
-            final float vscroll;
-            if (MotionEventCompat.isFromSource(event, InputDevice.SOURCE_CLASS_POINTER)) {
-                vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
-            } else if (MotionEventCompat.isFromSource(event, InputDevice.SOURCE_ROTARY_ENCODER)) {
-                vscroll = event.getAxisValue(MotionEvent.AXIS_SCROLL);
+    public boolean onGenericMotionEvent(@NonNull MotionEvent motionEvent) {
+        if (motionEvent.getAction() == MotionEvent.ACTION_SCROLL && !mIsBeingDragged) {
+            final float verticalScroll;
+            final int x;
+
+            if (MotionEventCompat.isFromSource(motionEvent, InputDevice.SOURCE_CLASS_POINTER)) {
+                verticalScroll = motionEvent.getAxisValue(MotionEvent.AXIS_VSCROLL);
+                x = (int) motionEvent.getX();
+            } else if (
+                    MotionEventCompat.isFromSource(motionEvent, InputDevice.SOURCE_ROTARY_ENCODER)
+            ) {
+                verticalScroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL);
+                // Since a Wear rotary event doesn't have a true X and we want to support proper
+                // overscroll animations, we put the x at the center of the screen.
+                x = getWidth() / 2;
             } else {
-                vscroll = 0;
+                verticalScroll = 0;
+                x = 0;
             }
-            if (vscroll != 0) {
-                final int delta = (int) (vscroll * getVerticalScrollFactorCompat());
-                final int range = getScrollRange();
-                int oldScrollY = getScrollY();
-                int newScrollY = oldScrollY - delta;
-                boolean absorbed = false;
-                if (newScrollY < 0) {
-                    // We don't want an EdgeEffect for mouse wheel operations
-                    final boolean canOverScroll = canOverScroll()
-                            && !MotionEventCompat.isFromSource(event, InputDevice.SOURCE_MOUSE);
-                    if (canOverScroll) {
-                        EdgeEffectCompat.onPullDistance(mEdgeGlowTop,
-                                -((float) newScrollY) / getHeight(),
-                                0.5f);
-                        mEdgeGlowTop.onRelease();
-                        invalidate();
-                        absorbed = true;
-                    }
-                    newScrollY = 0;
-                } else if (newScrollY > range) {
-                    // We don't want an EdgeEffect for mouse wheel operations
-                    final boolean canOverScroll = canOverScroll()
-                            && !MotionEventCompat.isFromSource(event, InputDevice.SOURCE_MOUSE);
-                    if (canOverScroll) {
-                        EdgeEffectCompat.onPullDistance(mEdgeGlowBottom,
-                                ((float) (newScrollY - range)) / getHeight(),
-                                0.5f);
-                        mEdgeGlowBottom.onRelease();
-                        invalidate();
-                        absorbed = true;
-                    }
-                    newScrollY = range;
-                }
-                if (newScrollY != oldScrollY) {
-                    super.scrollTo(getScrollX(), newScrollY);
-                    return true;
-                }
-                return absorbed;
+
+            if (verticalScroll != 0) {
+                // Rotary and Mouse scrolls are inverted from a touch scroll.
+                final int invertedDelta = (int) (verticalScroll * getVerticalScrollFactorCompat());
+
+                final boolean isSourceMouse =
+                        MotionEventCompat.isFromSource(motionEvent, InputDevice.SOURCE_MOUSE);
+
+                scrollBy(-invertedDelta, x, ViewCompat.TYPE_NON_TOUCH, isSourceMouse);
+
+                return true;
             }
         }
         return false;
@@ -1253,11 +1371,13 @@
             int scrollRangeX, int scrollRangeY,
             int maxOverScrollX, int maxOverScrollY,
             boolean isTouchEvent) {
+
         final int overScrollMode = getOverScrollMode();
         final boolean canScrollHorizontal =
                 computeHorizontalScrollRange() > computeHorizontalScrollExtent();
         final boolean canScrollVertical =
                 computeVerticalScrollRange() > computeVerticalScrollExtent();
+
         final boolean overScrollHorizontal = overScrollMode == View.OVER_SCROLL_ALWAYS
                 || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
         final boolean overScrollVertical = overScrollMode == View.OVER_SCROLL_ALWAYS
@@ -2171,16 +2291,6 @@
         }
     }
 
-    private void endDrag() {
-        mIsBeingDragged = false;
-
-        recycleVelocityTracker();
-        stopNestedScroll(ViewCompat.TYPE_TOUCH);
-
-        mEdgeGlowTop.onRelease();
-        mEdgeGlowBottom.onRelease();
-    }
-
     /**
      * {@inheritDoc}
      *
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
index 6e59651..a3e6a53 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
@@ -16,6 +16,7 @@
 
 package androidx.credentials.playservices
 
+import android.annotation.SuppressLint
 import android.app.Activity
 import android.os.CancellationSignal
 import android.util.Log
@@ -27,6 +28,10 @@
 import androidx.credentials.CredentialProvider
 import androidx.credentials.GetCredentialRequest
 import androidx.credentials.GetCredentialResponse
+import androidx.credentials.exceptions.CreateCredentialException
+import androidx.credentials.exceptions.CreateCredentialUnknownException
+import androidx.credentials.exceptions.GetCredentialException
+import androidx.credentials.exceptions.GetCredentialUnknownException
 import androidx.credentials.playservices.controllers.CreatePassword.CredentialProviderCreatePasswordController
 import java.util.concurrent.Executor
 
@@ -43,11 +48,18 @@
         activity: Activity?,
         cancellationSignal: CancellationSignal?,
         executor: Executor,
-        callback: CredentialManagerCallback<GetCredentialResponse>
+        callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>
     ) {
-        if (cancellationSignal != null) {
-            Log.i(TAG, "onCreateCredential cancellationSignal not used")
-            TODO("Use Cancel Operations Properly")
+        if (activity == null) {
+            executor.execute { callback.onError(
+                GetCredentialUnknownException("activity should" +
+                "not be null")
+            ) }
+            return
+        }
+        val fragmentManager: android.app.FragmentManager = activity.fragmentManager
+        if (cancellationReviewer(fragmentManager, cancellationSignal)) {
+            return
         }
         TODO("Not yet implemented")
     }
@@ -57,13 +69,17 @@
         activity: Activity?,
         cancellationSignal: CancellationSignal?,
         executor: Executor,
-        callback: CredentialManagerCallback<CreateCredentialResponse>
+        callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>
     ) {
-        if (cancellationSignal != null) {
-            Log.i(TAG, "onCreateCredential cancellationSignal not used")
-            TODO("Use Cancel Operations Properly")
+        if (activity == null) {
+            executor.execute { callback.onError(CreateCredentialUnknownException("activity should" +
+                "not be null")) }
+            return
         }
-        val fragmentManager: android.app.FragmentManager = activity!!.fragmentManager
+        val fragmentManager: android.app.FragmentManager = activity.fragmentManager
+        if (cancellationReviewer(fragmentManager, cancellationSignal)) {
+            return
+        }
         // TODO("Manage Fragment Lifecycle and Fragment Manager Properly")
         if (request is CreatePasswordRequest) {
             CredentialProviderCreatePasswordController.getInstance(
@@ -79,6 +95,27 @@
         }
     }
 
+    @SuppressLint("ClassVerificationFailure", "NewApi")
+    private fun cancellationReviewer(
+        fragmentManager: android.app.FragmentManager,
+        cancellationSignal: CancellationSignal?
+    ): Boolean {
+        if (cancellationSignal != null) {
+            if (cancellationSignal.isCanceled) {
+                Log.i(TAG, "Create credential already canceled before activity UI")
+                return true
+            }
+            cancellationSignal.setOnCancelListener {
+                // When this callback is notified, fragment manager may have fragments
+                fragmentManager.fragments.forEach { f ->
+                    f?.parentFragment?.fragmentManager?.beginTransaction()?.remove(f)
+                        ?.commitAllowingStateLoss()
+                }
+            }
+        }
+        return false
+    }
+
     override fun isAvailableOnDevice(): Boolean {
         TODO("Not yet implemented")
     }
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
index 994429a..e38cca7 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/CredentialProviderBeginSignInController.kt
@@ -21,6 +21,7 @@
 import androidx.credentials.CredentialManagerCallback
 import androidx.credentials.GetCredentialRequest
 import androidx.credentials.GetCredentialResponse
+import androidx.credentials.exceptions.GetCredentialException
 import androidx.credentials.playservices.controllers.CredentialProviderController
 import com.google.android.gms.auth.api.identity.BeginSignInRequest
 import com.google.android.gms.auth.api.identity.SignInCredential
@@ -36,12 +37,14 @@
     GetCredentialRequest,
     BeginSignInRequest,
     SignInCredential,
-    GetCredentialResponse>() {
+    GetCredentialResponse,
+    GetCredentialException>() {
 
     /**
      * The callback object state, used in the protected handleResponse method.
      */
-    private lateinit var callback: CredentialManagerCallback<GetCredentialResponse>
+    private lateinit var callback: CredentialManagerCallback<GetCredentialResponse,
+        GetCredentialException>
     /**
      * The callback requires an executor to invoke it.
      */
@@ -49,7 +52,7 @@
 
     override fun invokePlayServices(
         request: GetCredentialRequest,
-        callback: CredentialManagerCallback<GetCredentialResponse>,
+        callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
         executor: Executor
     ) {
         TODO("Not yet implemented")
@@ -65,11 +68,12 @@
         TODO("Not yet implemented")
     }
 
-    override fun convertToPlayServices(request: GetCredentialRequest): BeginSignInRequest {
+    override fun convertRequestToPlayServices(request: GetCredentialRequest): BeginSignInRequest {
         TODO("Not yet implemented")
     }
 
-    override fun convertToCredentialProvider(response: SignInCredential): GetCredentialResponse {
+    override fun convertResponseToCredentialManager(response: SignInCredential):
+        GetCredentialResponse {
         TODO("Not yet implemented")
     }
 
@@ -79,8 +83,8 @@
         // TODO("Ensure this works with the lifecycle")
 
         /**
-         * This finds a past version of the BeginSignInController if it exists, otherwise
-         * it generates a new instance.
+         * This finds a past version of the [CredentialProviderBeginSignInController] if it exists,
+         * otherwise it generates a new instance.
          *
          * @param fragmentManager a fragment manager pulled from an android activity
          * @return a credential provider controller for a specific credential request
@@ -103,11 +107,15 @@
             requestCode: Int,
             fragmentManager: android.app.FragmentManager
         ): CredentialProviderBeginSignInController? {
+            val oldFragment = fragmentManager.findFragmentByTag(requestCode.toString())
             try {
-                return fragmentManager.findFragmentByTag(requestCode.toString())
-                    as CredentialProviderBeginSignInController?
+                return oldFragment as CredentialProviderBeginSignInController
             } catch (e: Exception) {
-                Log.i(TAG, "Old fragment found of different type - replacement required")
+                Log.i(TAG,
+                    "Error with old fragment or null - replacement required")
+                if (oldFragment != null) {
+                    fragmentManager.beginTransaction().remove(oldFragment).commitAllowingStateLoss()
+                }
                 // TODO("Ensure this is well tested for fragment issues")
                 return null
             }
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
index 7015a9a..2261081 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePassword/CredentialProviderCreatePasswordController.kt
@@ -21,6 +21,7 @@
 import androidx.credentials.CreateCredentialResponse
 import androidx.credentials.CreatePasswordRequest
 import androidx.credentials.CredentialManagerCallback
+import androidx.credentials.exceptions.CreateCredentialException
 import androidx.credentials.playservices.controllers.CredentialProviderController
 import com.google.android.gms.auth.api.identity.SavePasswordRequest
 import java.util.concurrent.Executor
@@ -34,13 +35,15 @@
 class CredentialProviderCreatePasswordController : CredentialProviderController<
         CreatePasswordRequest,
         SavePasswordRequest,
-        Intent,
-        CreateCredentialResponse>() {
+        Unit,
+        CreateCredentialResponse,
+        CreateCredentialException>() {
 
     /**
      * The callback object state, used in the protected handleResponse method.
      */
-    private lateinit var callback: CredentialManagerCallback<CreateCredentialResponse>
+    private lateinit var callback: CredentialManagerCallback<CreateCredentialResponse,
+        CreateCredentialException>
 
     /**
      * The callback requires an executor to invoke it.
@@ -49,7 +52,7 @@
 
     override fun invokePlayServices(
         request: CreatePasswordRequest,
-        callback: CredentialManagerCallback<CreateCredentialResponse>,
+        callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>,
         executor: Executor
     ) {
         TODO("Not yet implemented")
@@ -65,11 +68,11 @@
         TODO("Not yet implemented")
     }
 
-    override fun convertToPlayServices(request: CreatePasswordRequest): SavePasswordRequest {
+    override fun convertRequestToPlayServices(request: CreatePasswordRequest): SavePasswordRequest {
         TODO("Not yet implemented")
     }
 
-    override fun convertToCredentialProvider(response: Intent): CreateCredentialResponse {
+    override fun convertResponseToCredentialManager(response: Unit): CreateCredentialResponse {
         TODO("Not yet implemented")
     }
 
@@ -104,11 +107,14 @@
             requestCode: Int,
             fragmentManager: android.app.FragmentManager
         ): CredentialProviderCreatePasswordController? {
+            val oldFragment = fragmentManager.findFragmentByTag(requestCode.toString())
             try {
-                return fragmentManager.findFragmentByTag(requestCode.toString())
-                    as CredentialProviderCreatePasswordController
+                return oldFragment as CredentialProviderCreatePasswordController
             } catch (e: Exception) {
-                Log.i(TAG, "Old fragment found of different type - replacement required")
+                Log.i(TAG, "Error with old fragment or null - replacement required")
+                if (oldFragment != null) {
+                    fragmentManager.beginTransaction().remove(oldFragment).commitAllowingStateLoss()
+                }
                 // TODO("Ensure this is well tested for fragment issues")
                 return null
             }
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
index d6e8763..204b12f 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
@@ -21,6 +21,7 @@
 import androidx.credentials.CreateCredentialResponse
 import androidx.credentials.CreatePublicKeyCredentialRequest
 import androidx.credentials.CredentialManagerCallback
+import androidx.credentials.exceptions.CreateCredentialException
 import androidx.credentials.playservices.controllers.CredentialProviderController
 import com.google.android.gms.fido.fido2.api.common.PublicKeyCredential
 import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions
@@ -37,12 +38,14 @@
             CreatePublicKeyCredentialRequest,
             PublicKeyCredentialCreationOptions,
             PublicKeyCredential,
-            CreateCredentialResponse>() {
+            CreateCredentialResponse,
+            CreateCredentialException>() {
 
     /**
      * The callback object state, used in the protected handleResponse method.
      */
-    private lateinit var callback: CredentialManagerCallback<CreateCredentialResponse>
+    private lateinit var callback: CredentialManagerCallback<CreateCredentialResponse,
+        CreateCredentialException>
 
     /**
      * The callback requires an executor to invoke it.
@@ -51,7 +54,7 @@
 
     override fun invokePlayServices(
         request: CreatePublicKeyCredentialRequest,
-        callback: CredentialManagerCallback<CreateCredentialResponse>,
+        callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>,
         executor: Executor
     ) {
         TODO("Not yet implemented")
@@ -67,12 +70,12 @@
         TODO("Not yet implemented")
     }
 
-    override fun convertToPlayServices(request: CreatePublicKeyCredentialRequest):
+    override fun convertRequestToPlayServices(request: CreatePublicKeyCredentialRequest):
         PublicKeyCredentialCreationOptions {
         TODO("Not yet implemented")
     }
 
-    override fun convertToCredentialProvider(response: PublicKeyCredential):
+    override fun convertResponseToCredentialManager(response: PublicKeyCredential):
         CreateCredentialResponse {
         TODO("Not yet implemented")
     }
@@ -110,11 +113,16 @@
             requestCode: Int,
             fragmentManager: android.app.FragmentManager
         ): CredentialProviderCreatePublicKeyCredentialController? {
+            val oldFragment = fragmentManager.findFragmentByTag(requestCode.toString())
             try {
-                return fragmentManager.findFragmentByTag(requestCode.toString())
-                    as CredentialProviderCreatePublicKeyCredentialController
+                return oldFragment as CredentialProviderCreatePublicKeyCredentialController
             } catch (e: Exception) {
-                Log.i(TAG, "Old fragment found of different type - replacement required")
+                Log.i(
+                    TAG,
+                    "Error with old fragment or null - replacement required")
+                if (oldFragment != null) {
+                    fragmentManager.beginTransaction().remove(oldFragment).commitAllowingStateLoss()
+                }
                 // TODO("Ensure this is well tested for fragment issues")
                 return null
             }
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderController.kt
index 12ab060..ccaf643 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderController.kt
@@ -17,6 +17,8 @@
 package androidx.credentials.playservices.controllers
 
 import androidx.credentials.CredentialManagerCallback
+import com.google.android.gms.common.api.CommonStatusCodes.INTERNAL_ERROR
+import com.google.android.gms.common.api.CommonStatusCodes.NETWORK_ERROR
 import java.util.concurrent.Executor
 
 /**
@@ -29,13 +31,18 @@
  * @param T2 the credential request type converted to play services
  * @param R2 the credential response type from play services
  * @param R1 the credential response type converted back to that used by credential manager
+ * @param E1 the credential error type to throw
  *
  * @hide
  */
 @Suppress("deprecation")
-abstract class CredentialProviderController<T1 : Any, T2 : Any, R2 : Any, R1 : Any> : android.app
+abstract class CredentialProviderController<T1 : Any, T2 : Any, R2 : Any, R1 : Any,
+    E1 : Any> : android.app
         .Fragment() {
 
+    protected var retryables: Set<Int> = setOf(INTERNAL_ERROR,
+        NETWORK_ERROR)
+
     /**
      * Invokes the flow that starts retrieving credential data. In this use case, we invoke
      * play service modules.
@@ -46,7 +53,7 @@
      */
     abstract fun invokePlayServices(
         request: T1,
-        callback: CredentialManagerCallback<R1>,
+        callback: CredentialManagerCallback<R1, E1>,
         executor: Executor
     )
 
@@ -56,7 +63,7 @@
      * @param request a credential provider request
      * @return a play service request
      */
-    protected abstract fun convertToPlayServices(request: T1): T2
+    protected abstract fun convertRequestToPlayServices(request: T1): T2
 
     /**
      * Allows converting from a play service response to a credential provider response.
@@ -64,5 +71,5 @@
      * @param response a play service response
      * @return a credential provider response
      */
-    protected abstract fun convertToCredentialProvider(response: R2): R1
+    protected abstract fun convertResponseToCredentialManager(response: R2): R1
 }
\ No newline at end of file
diff --git a/credentials/credentials/api/current.txt b/credentials/credentials/api/current.txt
index b14a46d..6d51309 100644
--- a/credentials/credentials/api/current.txt
+++ b/credentials/credentials/api/current.txt
@@ -1,22 +1,16 @@
 // Signature format: 4.0
 package androidx.credentials {
 
+  public final class ClearCredentialStateRequest {
+    ctor public ClearCredentialStateRequest();
+  }
+
   public class CreateCredentialRequest {
     ctor public CreateCredentialRequest(String type, android.os.Bundle data, boolean requireSystemProvider);
-    method public final android.os.Bundle getData();
-    method public final boolean getRequireSystemProvider();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final boolean requireSystemProvider;
-    property public final String type;
   }
 
   public class CreateCredentialResponse {
     ctor public CreateCredentialResponse(String type, android.os.Bundle data);
-    method public final android.os.Bundle getData();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final String type;
   }
 
   public final class CreatePasswordRequest extends androidx.credentials.CreateCredentialRequest {
@@ -31,20 +25,55 @@
     ctor public CreatePasswordResponse();
   }
 
+  public final class CreatePublicKeyCredentialRequest extends androidx.credentials.CreateCredentialRequest {
+    ctor public CreatePublicKeyCredentialRequest(String requestJson, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequest(String requestJson);
+    method public boolean getAllowHybrid();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String requestJson;
+  }
+
+  public final class CreatePublicKeyCredentialRequestPrivileged extends androidx.credentials.CreateCredentialRequest {
+    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash);
+    method public boolean getAllowHybrid();
+    method public String getClientDataHash();
+    method public String getRelyingParty();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String clientDataHash;
+    property public final String relyingParty;
+    property public final String requestJson;
+  }
+
+  public static final class CreatePublicKeyCredentialRequestPrivileged.Builder {
+    ctor public CreatePublicKeyCredentialRequestPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged build();
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setAllowHybrid(boolean allowHybrid);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRelyingParty(String relyingParty);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRequestJson(String requestJson);
+  }
+
+  public final class CreatePublicKeyCredentialResponse extends androidx.credentials.CreateCredentialResponse {
+    ctor public CreatePublicKeyCredentialResponse(String registrationResponseJson);
+    method public String getRegistrationResponseJson();
+    property public final String registrationResponseJson;
+  }
+
   public class Credential {
     ctor public Credential(String type, android.os.Bundle data);
-    method public final android.os.Bundle getData();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final String type;
   }
 
   public final class CredentialManager {
+    method public suspend Object? clearCredentialState(androidx.credentials.ClearCredentialStateRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public void clearCredentialStateAsync(androidx.credentials.ClearCredentialStateRequest request, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
     method public static androidx.credentials.CredentialManager create(android.content.Context context);
     method public suspend Object? executeCreateCredential(androidx.credentials.CreateCredentialRequest request, optional android.app.Activity? activity, optional kotlin.coroutines.Continuation<? super androidx.credentials.CreateCredentialResponse>);
-    method public void executeCreateCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse> callback);
+    method public void executeCreateCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse,androidx.credentials.exceptions.CreateCredentialException> callback);
     method public suspend Object? executeGetCredential(androidx.credentials.GetCredentialRequest request, optional android.app.Activity? activity, optional kotlin.coroutines.Continuation<? super androidx.credentials.GetCredentialResponse>);
-    method public void executeGetCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse> callback);
+    method public void executeGetCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse,androidx.credentials.exceptions.GetCredentialException> callback);
     field public static final androidx.credentials.CredentialManager.Companion Companion;
   }
 
@@ -52,39 +81,29 @@
     method public androidx.credentials.CredentialManager create(android.content.Context context);
   }
 
-  public interface CredentialManagerCallback<R> {
-    method public default void onError(androidx.credentials.CredentialManagerException e);
-    method public void onResult(R? result);
-  }
-
-  public final class CredentialManagerException extends java.lang.Exception {
-    ctor public CredentialManagerException(int errorCode, optional CharSequence? errorMessage);
-    method public int getErrorCode();
-    method public CharSequence? getErrorMessage();
-    property public final int errorCode;
-    property public final CharSequence? errorMessage;
+  public interface CredentialManagerCallback<R, E> {
+    method public void onError(E e);
+    method public void onResult(R result);
   }
 
   public class GetCredentialOption {
     ctor public GetCredentialOption(String type, android.os.Bundle data, boolean requireSystemProvider);
-    method public final android.os.Bundle getData();
-    method public final boolean getRequireSystemProvider();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final boolean requireSystemProvider;
-    property public final String type;
   }
 
   public final class GetCredentialRequest {
+    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions, optional boolean isAutoSelectAllowed);
     ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
     method public java.util.List<androidx.credentials.GetCredentialOption> getGetCredentialOptions();
+    method public boolean isAutoSelectAllowed();
     property public final java.util.List<androidx.credentials.GetCredentialOption> getCredentialOptions;
+    property public final boolean isAutoSelectAllowed;
   }
 
   public static final class GetCredentialRequest.Builder {
     ctor public GetCredentialRequest.Builder();
     method public androidx.credentials.GetCredentialRequest.Builder addGetCredentialOption(androidx.credentials.GetCredentialOption getCredentialOption);
     method public androidx.credentials.GetCredentialRequest build();
+    method public androidx.credentials.GetCredentialRequest.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
     method public androidx.credentials.GetCredentialRequest.Builder setGetCredentialOptions(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
   }
 
@@ -98,6 +117,37 @@
     ctor public GetPasswordOption();
   }
 
+  public final class GetPublicKeyCredentialOption extends androidx.credentials.GetCredentialOption {
+    ctor public GetPublicKeyCredentialOption(String requestJson, optional boolean allowHybrid);
+    ctor public GetPublicKeyCredentialOption(String requestJson);
+    method public boolean getAllowHybrid();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String requestJson;
+  }
+
+  public final class GetPublicKeyCredentialOptionPrivileged extends androidx.credentials.GetCredentialOption {
+    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash);
+    method public boolean getAllowHybrid();
+    method public String getClientDataHash();
+    method public String getRelyingParty();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String clientDataHash;
+    property public final String relyingParty;
+    property public final String requestJson;
+  }
+
+  public static final class GetPublicKeyCredentialOptionPrivileged.Builder {
+    ctor public GetPublicKeyCredentialOptionPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged build();
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setAllowHybrid(boolean allowHybrid);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRelyingParty(String relyingParty);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRequestJson(String requestJson);
+  }
+
   public final class PasswordCredential extends androidx.credentials.Credential {
     ctor public PasswordCredential(String id, String password);
     method public String getId();
@@ -106,5 +156,76 @@
     property public final String password;
   }
 
+  public final class PublicKeyCredential extends androidx.credentials.Credential {
+    ctor public PublicKeyCredential(String authenticationResponseJson);
+    method public String getAuthenticationResponseJson();
+    property public final String authenticationResponseJson;
+  }
+
+}
+
+package androidx.credentials.exceptions {
+
+  public class ClearCredentialException extends java.lang.Exception {
+    ctor public ClearCredentialException(String type, optional CharSequence? errorMessage);
+    ctor public ClearCredentialException(String type);
+    method public final CharSequence? getErrorMessage();
+    property public final CharSequence? errorMessage;
+  }
+
+  public final class ClearCredentialInterruptedException extends androidx.credentials.exceptions.ClearCredentialException {
+    ctor public ClearCredentialInterruptedException(optional CharSequence? errorMessage);
+    ctor public ClearCredentialInterruptedException();
+  }
+
+  public final class ClearCredentialUnknownException extends androidx.credentials.exceptions.ClearCredentialException {
+    ctor public ClearCredentialUnknownException(optional CharSequence? errorMessage);
+    ctor public ClearCredentialUnknownException();
+  }
+
+  public final class CreateCredentialCanceledException extends androidx.credentials.exceptions.CreateCredentialException {
+    ctor public CreateCredentialCanceledException(optional CharSequence? errorMessage);
+    ctor public CreateCredentialCanceledException();
+  }
+
+  public class CreateCredentialException extends java.lang.Exception {
+    ctor public CreateCredentialException(String type, optional CharSequence? errorMessage);
+    ctor public CreateCredentialException(String type);
+    method public final CharSequence? getErrorMessage();
+    property public final CharSequence? errorMessage;
+  }
+
+  public final class CreateCredentialInterruptedException extends androidx.credentials.exceptions.CreateCredentialException {
+    ctor public CreateCredentialInterruptedException(optional CharSequence? errorMessage);
+    ctor public CreateCredentialInterruptedException();
+  }
+
+  public final class CreateCredentialUnknownException extends androidx.credentials.exceptions.CreateCredentialException {
+    ctor public CreateCredentialUnknownException(optional CharSequence? errorMessage);
+    ctor public CreateCredentialUnknownException();
+  }
+
+  public final class GetCredentialCanceledException extends androidx.credentials.exceptions.GetCredentialException {
+    ctor public GetCredentialCanceledException(optional CharSequence? errorMessage);
+    ctor public GetCredentialCanceledException();
+  }
+
+  public class GetCredentialException extends java.lang.Exception {
+    ctor public GetCredentialException(String type, optional CharSequence? errorMessage);
+    ctor public GetCredentialException(String type);
+    method public final CharSequence? getErrorMessage();
+    property public final CharSequence? errorMessage;
+  }
+
+  public final class GetCredentialInterruptedException extends androidx.credentials.exceptions.GetCredentialException {
+    ctor public GetCredentialInterruptedException(optional CharSequence? errorMessage);
+    ctor public GetCredentialInterruptedException();
+  }
+
+  public final class GetCredentialUnknownException extends androidx.credentials.exceptions.GetCredentialException {
+    ctor public GetCredentialUnknownException(optional CharSequence? errorMessage);
+    ctor public GetCredentialUnknownException();
+  }
+
 }
 
diff --git a/credentials/credentials/api/public_plus_experimental_current.txt b/credentials/credentials/api/public_plus_experimental_current.txt
index b14a46d..6d51309 100644
--- a/credentials/credentials/api/public_plus_experimental_current.txt
+++ b/credentials/credentials/api/public_plus_experimental_current.txt
@@ -1,22 +1,16 @@
 // Signature format: 4.0
 package androidx.credentials {
 
+  public final class ClearCredentialStateRequest {
+    ctor public ClearCredentialStateRequest();
+  }
+
   public class CreateCredentialRequest {
     ctor public CreateCredentialRequest(String type, android.os.Bundle data, boolean requireSystemProvider);
-    method public final android.os.Bundle getData();
-    method public final boolean getRequireSystemProvider();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final boolean requireSystemProvider;
-    property public final String type;
   }
 
   public class CreateCredentialResponse {
     ctor public CreateCredentialResponse(String type, android.os.Bundle data);
-    method public final android.os.Bundle getData();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final String type;
   }
 
   public final class CreatePasswordRequest extends androidx.credentials.CreateCredentialRequest {
@@ -31,20 +25,55 @@
     ctor public CreatePasswordResponse();
   }
 
+  public final class CreatePublicKeyCredentialRequest extends androidx.credentials.CreateCredentialRequest {
+    ctor public CreatePublicKeyCredentialRequest(String requestJson, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequest(String requestJson);
+    method public boolean getAllowHybrid();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String requestJson;
+  }
+
+  public final class CreatePublicKeyCredentialRequestPrivileged extends androidx.credentials.CreateCredentialRequest {
+    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash);
+    method public boolean getAllowHybrid();
+    method public String getClientDataHash();
+    method public String getRelyingParty();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String clientDataHash;
+    property public final String relyingParty;
+    property public final String requestJson;
+  }
+
+  public static final class CreatePublicKeyCredentialRequestPrivileged.Builder {
+    ctor public CreatePublicKeyCredentialRequestPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged build();
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setAllowHybrid(boolean allowHybrid);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRelyingParty(String relyingParty);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRequestJson(String requestJson);
+  }
+
+  public final class CreatePublicKeyCredentialResponse extends androidx.credentials.CreateCredentialResponse {
+    ctor public CreatePublicKeyCredentialResponse(String registrationResponseJson);
+    method public String getRegistrationResponseJson();
+    property public final String registrationResponseJson;
+  }
+
   public class Credential {
     ctor public Credential(String type, android.os.Bundle data);
-    method public final android.os.Bundle getData();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final String type;
   }
 
   public final class CredentialManager {
+    method public suspend Object? clearCredentialState(androidx.credentials.ClearCredentialStateRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public void clearCredentialStateAsync(androidx.credentials.ClearCredentialStateRequest request, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
     method public static androidx.credentials.CredentialManager create(android.content.Context context);
     method public suspend Object? executeCreateCredential(androidx.credentials.CreateCredentialRequest request, optional android.app.Activity? activity, optional kotlin.coroutines.Continuation<? super androidx.credentials.CreateCredentialResponse>);
-    method public void executeCreateCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse> callback);
+    method public void executeCreateCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse,androidx.credentials.exceptions.CreateCredentialException> callback);
     method public suspend Object? executeGetCredential(androidx.credentials.GetCredentialRequest request, optional android.app.Activity? activity, optional kotlin.coroutines.Continuation<? super androidx.credentials.GetCredentialResponse>);
-    method public void executeGetCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse> callback);
+    method public void executeGetCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse,androidx.credentials.exceptions.GetCredentialException> callback);
     field public static final androidx.credentials.CredentialManager.Companion Companion;
   }
 
@@ -52,39 +81,29 @@
     method public androidx.credentials.CredentialManager create(android.content.Context context);
   }
 
-  public interface CredentialManagerCallback<R> {
-    method public default void onError(androidx.credentials.CredentialManagerException e);
-    method public void onResult(R? result);
-  }
-
-  public final class CredentialManagerException extends java.lang.Exception {
-    ctor public CredentialManagerException(int errorCode, optional CharSequence? errorMessage);
-    method public int getErrorCode();
-    method public CharSequence? getErrorMessage();
-    property public final int errorCode;
-    property public final CharSequence? errorMessage;
+  public interface CredentialManagerCallback<R, E> {
+    method public void onError(E e);
+    method public void onResult(R result);
   }
 
   public class GetCredentialOption {
     ctor public GetCredentialOption(String type, android.os.Bundle data, boolean requireSystemProvider);
-    method public final android.os.Bundle getData();
-    method public final boolean getRequireSystemProvider();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final boolean requireSystemProvider;
-    property public final String type;
   }
 
   public final class GetCredentialRequest {
+    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions, optional boolean isAutoSelectAllowed);
     ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
     method public java.util.List<androidx.credentials.GetCredentialOption> getGetCredentialOptions();
+    method public boolean isAutoSelectAllowed();
     property public final java.util.List<androidx.credentials.GetCredentialOption> getCredentialOptions;
+    property public final boolean isAutoSelectAllowed;
   }
 
   public static final class GetCredentialRequest.Builder {
     ctor public GetCredentialRequest.Builder();
     method public androidx.credentials.GetCredentialRequest.Builder addGetCredentialOption(androidx.credentials.GetCredentialOption getCredentialOption);
     method public androidx.credentials.GetCredentialRequest build();
+    method public androidx.credentials.GetCredentialRequest.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
     method public androidx.credentials.GetCredentialRequest.Builder setGetCredentialOptions(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
   }
 
@@ -98,6 +117,37 @@
     ctor public GetPasswordOption();
   }
 
+  public final class GetPublicKeyCredentialOption extends androidx.credentials.GetCredentialOption {
+    ctor public GetPublicKeyCredentialOption(String requestJson, optional boolean allowHybrid);
+    ctor public GetPublicKeyCredentialOption(String requestJson);
+    method public boolean getAllowHybrid();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String requestJson;
+  }
+
+  public final class GetPublicKeyCredentialOptionPrivileged extends androidx.credentials.GetCredentialOption {
+    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash);
+    method public boolean getAllowHybrid();
+    method public String getClientDataHash();
+    method public String getRelyingParty();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String clientDataHash;
+    property public final String relyingParty;
+    property public final String requestJson;
+  }
+
+  public static final class GetPublicKeyCredentialOptionPrivileged.Builder {
+    ctor public GetPublicKeyCredentialOptionPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged build();
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setAllowHybrid(boolean allowHybrid);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRelyingParty(String relyingParty);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRequestJson(String requestJson);
+  }
+
   public final class PasswordCredential extends androidx.credentials.Credential {
     ctor public PasswordCredential(String id, String password);
     method public String getId();
@@ -106,5 +156,76 @@
     property public final String password;
   }
 
+  public final class PublicKeyCredential extends androidx.credentials.Credential {
+    ctor public PublicKeyCredential(String authenticationResponseJson);
+    method public String getAuthenticationResponseJson();
+    property public final String authenticationResponseJson;
+  }
+
+}
+
+package androidx.credentials.exceptions {
+
+  public class ClearCredentialException extends java.lang.Exception {
+    ctor public ClearCredentialException(String type, optional CharSequence? errorMessage);
+    ctor public ClearCredentialException(String type);
+    method public final CharSequence? getErrorMessage();
+    property public final CharSequence? errorMessage;
+  }
+
+  public final class ClearCredentialInterruptedException extends androidx.credentials.exceptions.ClearCredentialException {
+    ctor public ClearCredentialInterruptedException(optional CharSequence? errorMessage);
+    ctor public ClearCredentialInterruptedException();
+  }
+
+  public final class ClearCredentialUnknownException extends androidx.credentials.exceptions.ClearCredentialException {
+    ctor public ClearCredentialUnknownException(optional CharSequence? errorMessage);
+    ctor public ClearCredentialUnknownException();
+  }
+
+  public final class CreateCredentialCanceledException extends androidx.credentials.exceptions.CreateCredentialException {
+    ctor public CreateCredentialCanceledException(optional CharSequence? errorMessage);
+    ctor public CreateCredentialCanceledException();
+  }
+
+  public class CreateCredentialException extends java.lang.Exception {
+    ctor public CreateCredentialException(String type, optional CharSequence? errorMessage);
+    ctor public CreateCredentialException(String type);
+    method public final CharSequence? getErrorMessage();
+    property public final CharSequence? errorMessage;
+  }
+
+  public final class CreateCredentialInterruptedException extends androidx.credentials.exceptions.CreateCredentialException {
+    ctor public CreateCredentialInterruptedException(optional CharSequence? errorMessage);
+    ctor public CreateCredentialInterruptedException();
+  }
+
+  public final class CreateCredentialUnknownException extends androidx.credentials.exceptions.CreateCredentialException {
+    ctor public CreateCredentialUnknownException(optional CharSequence? errorMessage);
+    ctor public CreateCredentialUnknownException();
+  }
+
+  public final class GetCredentialCanceledException extends androidx.credentials.exceptions.GetCredentialException {
+    ctor public GetCredentialCanceledException(optional CharSequence? errorMessage);
+    ctor public GetCredentialCanceledException();
+  }
+
+  public class GetCredentialException extends java.lang.Exception {
+    ctor public GetCredentialException(String type, optional CharSequence? errorMessage);
+    ctor public GetCredentialException(String type);
+    method public final CharSequence? getErrorMessage();
+    property public final CharSequence? errorMessage;
+  }
+
+  public final class GetCredentialInterruptedException extends androidx.credentials.exceptions.GetCredentialException {
+    ctor public GetCredentialInterruptedException(optional CharSequence? errorMessage);
+    ctor public GetCredentialInterruptedException();
+  }
+
+  public final class GetCredentialUnknownException extends androidx.credentials.exceptions.GetCredentialException {
+    ctor public GetCredentialUnknownException(optional CharSequence? errorMessage);
+    ctor public GetCredentialUnknownException();
+  }
+
 }
 
diff --git a/credentials/credentials/api/restricted_current.txt b/credentials/credentials/api/restricted_current.txt
index b14a46d..6d51309 100644
--- a/credentials/credentials/api/restricted_current.txt
+++ b/credentials/credentials/api/restricted_current.txt
@@ -1,22 +1,16 @@
 // Signature format: 4.0
 package androidx.credentials {
 
+  public final class ClearCredentialStateRequest {
+    ctor public ClearCredentialStateRequest();
+  }
+
   public class CreateCredentialRequest {
     ctor public CreateCredentialRequest(String type, android.os.Bundle data, boolean requireSystemProvider);
-    method public final android.os.Bundle getData();
-    method public final boolean getRequireSystemProvider();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final boolean requireSystemProvider;
-    property public final String type;
   }
 
   public class CreateCredentialResponse {
     ctor public CreateCredentialResponse(String type, android.os.Bundle data);
-    method public final android.os.Bundle getData();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final String type;
   }
 
   public final class CreatePasswordRequest extends androidx.credentials.CreateCredentialRequest {
@@ -31,20 +25,55 @@
     ctor public CreatePasswordResponse();
   }
 
+  public final class CreatePublicKeyCredentialRequest extends androidx.credentials.CreateCredentialRequest {
+    ctor public CreatePublicKeyCredentialRequest(String requestJson, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequest(String requestJson);
+    method public boolean getAllowHybrid();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String requestJson;
+  }
+
+  public final class CreatePublicKeyCredentialRequestPrivileged extends androidx.credentials.CreateCredentialRequest {
+    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash);
+    method public boolean getAllowHybrid();
+    method public String getClientDataHash();
+    method public String getRelyingParty();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String clientDataHash;
+    property public final String relyingParty;
+    property public final String requestJson;
+  }
+
+  public static final class CreatePublicKeyCredentialRequestPrivileged.Builder {
+    ctor public CreatePublicKeyCredentialRequestPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged build();
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setAllowHybrid(boolean allowHybrid);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRelyingParty(String relyingParty);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRequestJson(String requestJson);
+  }
+
+  public final class CreatePublicKeyCredentialResponse extends androidx.credentials.CreateCredentialResponse {
+    ctor public CreatePublicKeyCredentialResponse(String registrationResponseJson);
+    method public String getRegistrationResponseJson();
+    property public final String registrationResponseJson;
+  }
+
   public class Credential {
     ctor public Credential(String type, android.os.Bundle data);
-    method public final android.os.Bundle getData();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final String type;
   }
 
   public final class CredentialManager {
+    method public suspend Object? clearCredentialState(androidx.credentials.ClearCredentialStateRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public void clearCredentialStateAsync(androidx.credentials.ClearCredentialStateRequest request, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
     method public static androidx.credentials.CredentialManager create(android.content.Context context);
     method public suspend Object? executeCreateCredential(androidx.credentials.CreateCredentialRequest request, optional android.app.Activity? activity, optional kotlin.coroutines.Continuation<? super androidx.credentials.CreateCredentialResponse>);
-    method public void executeCreateCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse> callback);
+    method public void executeCreateCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse,androidx.credentials.exceptions.CreateCredentialException> callback);
     method public suspend Object? executeGetCredential(androidx.credentials.GetCredentialRequest request, optional android.app.Activity? activity, optional kotlin.coroutines.Continuation<? super androidx.credentials.GetCredentialResponse>);
-    method public void executeGetCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse> callback);
+    method public void executeGetCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity? activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse,androidx.credentials.exceptions.GetCredentialException> callback);
     field public static final androidx.credentials.CredentialManager.Companion Companion;
   }
 
@@ -52,39 +81,29 @@
     method public androidx.credentials.CredentialManager create(android.content.Context context);
   }
 
-  public interface CredentialManagerCallback<R> {
-    method public default void onError(androidx.credentials.CredentialManagerException e);
-    method public void onResult(R? result);
-  }
-
-  public final class CredentialManagerException extends java.lang.Exception {
-    ctor public CredentialManagerException(int errorCode, optional CharSequence? errorMessage);
-    method public int getErrorCode();
-    method public CharSequence? getErrorMessage();
-    property public final int errorCode;
-    property public final CharSequence? errorMessage;
+  public interface CredentialManagerCallback<R, E> {
+    method public void onError(E e);
+    method public void onResult(R result);
   }
 
   public class GetCredentialOption {
     ctor public GetCredentialOption(String type, android.os.Bundle data, boolean requireSystemProvider);
-    method public final android.os.Bundle getData();
-    method public final boolean getRequireSystemProvider();
-    method public final String getType();
-    property public final android.os.Bundle data;
-    property public final boolean requireSystemProvider;
-    property public final String type;
   }
 
   public final class GetCredentialRequest {
+    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions, optional boolean isAutoSelectAllowed);
     ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
     method public java.util.List<androidx.credentials.GetCredentialOption> getGetCredentialOptions();
+    method public boolean isAutoSelectAllowed();
     property public final java.util.List<androidx.credentials.GetCredentialOption> getCredentialOptions;
+    property public final boolean isAutoSelectAllowed;
   }
 
   public static final class GetCredentialRequest.Builder {
     ctor public GetCredentialRequest.Builder();
     method public androidx.credentials.GetCredentialRequest.Builder addGetCredentialOption(androidx.credentials.GetCredentialOption getCredentialOption);
     method public androidx.credentials.GetCredentialRequest build();
+    method public androidx.credentials.GetCredentialRequest.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
     method public androidx.credentials.GetCredentialRequest.Builder setGetCredentialOptions(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
   }
 
@@ -98,6 +117,37 @@
     ctor public GetPasswordOption();
   }
 
+  public final class GetPublicKeyCredentialOption extends androidx.credentials.GetCredentialOption {
+    ctor public GetPublicKeyCredentialOption(String requestJson, optional boolean allowHybrid);
+    ctor public GetPublicKeyCredentialOption(String requestJson);
+    method public boolean getAllowHybrid();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String requestJson;
+  }
+
+  public final class GetPublicKeyCredentialOptionPrivileged extends androidx.credentials.GetCredentialOption {
+    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash);
+    method public boolean getAllowHybrid();
+    method public String getClientDataHash();
+    method public String getRelyingParty();
+    method public String getRequestJson();
+    property public final boolean allowHybrid;
+    property public final String clientDataHash;
+    property public final String relyingParty;
+    property public final String requestJson;
+  }
+
+  public static final class GetPublicKeyCredentialOptionPrivileged.Builder {
+    ctor public GetPublicKeyCredentialOptionPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged build();
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setAllowHybrid(boolean allowHybrid);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRelyingParty(String relyingParty);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRequestJson(String requestJson);
+  }
+
   public final class PasswordCredential extends androidx.credentials.Credential {
     ctor public PasswordCredential(String id, String password);
     method public String getId();
@@ -106,5 +156,76 @@
     property public final String password;
   }
 
+  public final class PublicKeyCredential extends androidx.credentials.Credential {
+    ctor public PublicKeyCredential(String authenticationResponseJson);
+    method public String getAuthenticationResponseJson();
+    property public final String authenticationResponseJson;
+  }
+
+}
+
+package androidx.credentials.exceptions {
+
+  public class ClearCredentialException extends java.lang.Exception {
+    ctor public ClearCredentialException(String type, optional CharSequence? errorMessage);
+    ctor public ClearCredentialException(String type);
+    method public final CharSequence? getErrorMessage();
+    property public final CharSequence? errorMessage;
+  }
+
+  public final class ClearCredentialInterruptedException extends androidx.credentials.exceptions.ClearCredentialException {
+    ctor public ClearCredentialInterruptedException(optional CharSequence? errorMessage);
+    ctor public ClearCredentialInterruptedException();
+  }
+
+  public final class ClearCredentialUnknownException extends androidx.credentials.exceptions.ClearCredentialException {
+    ctor public ClearCredentialUnknownException(optional CharSequence? errorMessage);
+    ctor public ClearCredentialUnknownException();
+  }
+
+  public final class CreateCredentialCanceledException extends androidx.credentials.exceptions.CreateCredentialException {
+    ctor public CreateCredentialCanceledException(optional CharSequence? errorMessage);
+    ctor public CreateCredentialCanceledException();
+  }
+
+  public class CreateCredentialException extends java.lang.Exception {
+    ctor public CreateCredentialException(String type, optional CharSequence? errorMessage);
+    ctor public CreateCredentialException(String type);
+    method public final CharSequence? getErrorMessage();
+    property public final CharSequence? errorMessage;
+  }
+
+  public final class CreateCredentialInterruptedException extends androidx.credentials.exceptions.CreateCredentialException {
+    ctor public CreateCredentialInterruptedException(optional CharSequence? errorMessage);
+    ctor public CreateCredentialInterruptedException();
+  }
+
+  public final class CreateCredentialUnknownException extends androidx.credentials.exceptions.CreateCredentialException {
+    ctor public CreateCredentialUnknownException(optional CharSequence? errorMessage);
+    ctor public CreateCredentialUnknownException();
+  }
+
+  public final class GetCredentialCanceledException extends androidx.credentials.exceptions.GetCredentialException {
+    ctor public GetCredentialCanceledException(optional CharSequence? errorMessage);
+    ctor public GetCredentialCanceledException();
+  }
+
+  public class GetCredentialException extends java.lang.Exception {
+    ctor public GetCredentialException(String type, optional CharSequence? errorMessage);
+    ctor public GetCredentialException(String type);
+    method public final CharSequence? getErrorMessage();
+    property public final CharSequence? errorMessage;
+  }
+
+  public final class GetCredentialInterruptedException extends androidx.credentials.exceptions.GetCredentialException {
+    ctor public GetCredentialInterruptedException(optional CharSequence? errorMessage);
+    ctor public GetCredentialInterruptedException();
+  }
+
+  public final class GetCredentialUnknownException extends androidx.credentials.exceptions.GetCredentialException {
+    ctor public GetCredentialUnknownException(optional CharSequence? errorMessage);
+    ctor public GetCredentialUnknownException();
+  }
+
 }
 
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestJavaTest.java
index 2604d86..b4e4124 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestJavaTest.java
@@ -16,8 +16,8 @@
 
 package androidx.credentials;
 
-import static androidx.credentials.CreatePublicKeyCredentialBaseRequest.BUNDLE_KEY_REQUEST_JSON;
 import static androidx.credentials.CreatePublicKeyCredentialRequest.BUNDLE_KEY_ALLOW_HYBRID;
+import static androidx.credentials.CreatePublicKeyCredentialRequest.BUNDLE_KEY_REQUEST_JSON;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedJavaTest.java
index 8558cbb..10e747e 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedJavaTest.java
@@ -16,10 +16,10 @@
 
 package androidx.credentials;
 
-import static androidx.credentials.CreatePublicKeyCredentialBaseRequest.BUNDLE_KEY_REQUEST_JSON;
 import static androidx.credentials.CreatePublicKeyCredentialRequest.BUNDLE_KEY_ALLOW_HYBRID;
 import static androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_CLIENT_DATA_HASH;
-import static androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_RP;
+import static androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_RELYING_PARTY;
+import static androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_REQUEST_JSON;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -43,14 +43,14 @@
     public void constructor_success() {
         new CreatePublicKeyCredentialRequestPrivileged(
                 "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                "RP", "ClientDataHash");
+                "relyingParty", "ClientDataHash");
     }
 
     @Test
     public void constructor_setsAllowHybridToTrueByDefault() {
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialRequestPrivileged =
                 new CreatePublicKeyCredentialRequestPrivileged(
-                        "JSON", "RP", "HASH");
+                        "JSON", "relyingParty", "HASH");
         boolean allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid();
         assertThat(allowHybridActual).isTrue();
     }
@@ -59,7 +59,9 @@
     public void constructor_setsAllowHybridToFalse() {
         boolean allowHybridExpected = false;
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialRequestPrivileged =
-                new CreatePublicKeyCredentialRequestPrivileged("JSON", "RP", "HASH",
+                new CreatePublicKeyCredentialRequestPrivileged("JSON",
+                        "relyingParty",
+                        "HASH",
                         allowHybridExpected);
         boolean allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid();
         assertThat(allowHybridActual).isEqualTo(allowHybridExpected);
@@ -69,7 +71,7 @@
     public void builder_build_defaultAllowHybrid_true() {
         CreatePublicKeyCredentialRequestPrivileged defaultPrivilegedRequest = new
                 CreatePublicKeyCredentialRequestPrivileged.Builder("{\"Data\":5}",
-                "RP", "HASH").build();
+                "relyingParty", "HASH").build();
         assertThat(defaultPrivilegedRequest.allowHybrid()).isTrue();
     }
 
@@ -77,7 +79,8 @@
     public void builder_build_nonDefaultAllowHybrid_false() {
         boolean allowHybridExpected = false;
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialRequestPrivileged =
-                new CreatePublicKeyCredentialRequestPrivileged.Builder("JSON", "RP", "HASH")
+                new CreatePublicKeyCredentialRequestPrivileged.Builder("JSON",
+                        "relyingParty", "HASH")
                         .setAllowHybrid(allowHybridExpected).build();
         boolean allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid();
         assertThat(allowHybridActual).isEqualTo(allowHybridExpected);
@@ -87,20 +90,22 @@
     public void getter_requestJson_success() {
         String testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialReqPriv =
-                new CreatePublicKeyCredentialRequestPrivileged(testJsonExpected, "RP", "HASH");
+                new CreatePublicKeyCredentialRequestPrivileged(testJsonExpected,
+                        "relyingParty", "HASH");
         String testJsonActual = createPublicKeyCredentialReqPriv.getRequestJson();
         assertThat(testJsonActual).isEqualTo(testJsonExpected);
     }
 
     @Test
-    public void getter_rp_success() {
-        String testRpExpected = "RP";
+    public void getter_relyingParty_success() {
+        String testrelyingPartyExpected = "relyingParty";
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialRequestPrivileged =
                 new CreatePublicKeyCredentialRequestPrivileged(
                         "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                        testRpExpected, "X342%4dfd7&");
-        String testRpActual = createPublicKeyCredentialRequestPrivileged.getRp();
-        assertThat(testRpActual).isEqualTo(testRpExpected);
+                        testrelyingPartyExpected, "X342%4dfd7&");
+        String testrelyingPartyActual = createPublicKeyCredentialRequestPrivileged
+                .getRelyingParty();
+        assertThat(testrelyingPartyActual).isEqualTo(testrelyingPartyExpected);
     }
 
     @Test
@@ -109,7 +114,7 @@
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialRequestPrivileged =
                 new CreatePublicKeyCredentialRequestPrivileged(
                         "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                         "RP", clientDataHashExpected);
+                         "relyingParty", clientDataHashExpected);
         String clientDataHashActual =
                 createPublicKeyCredentialRequestPrivileged.getClientDataHash();
         assertThat(clientDataHashActual).isEqualTo(clientDataHashExpected);
@@ -118,18 +123,18 @@
     @Test
     public void getter_frameworkProperties_success() {
         String requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
-        String rpExpected = "RP";
+        String relyingPartyExpected = "relyingParty";
         String clientDataHashExpected = "X342%4dfd7&";
         boolean allowHybridExpected = false;
         Bundle expectedData = new Bundle();
         expectedData.putString(BUNDLE_KEY_REQUEST_JSON, requestJsonExpected);
-        expectedData.putString(BUNDLE_KEY_RP, rpExpected);
+        expectedData.putString(BUNDLE_KEY_RELYING_PARTY, relyingPartyExpected);
         expectedData.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHashExpected);
         expectedData.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybridExpected);
 
         CreatePublicKeyCredentialRequestPrivileged request =
                 new CreatePublicKeyCredentialRequestPrivileged(
-                        requestJsonExpected, rpExpected, clientDataHashExpected,
+                        requestJsonExpected, relyingPartyExpected, clientDataHashExpected,
                         allowHybridExpected);
 
         assertThat(request.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedTest.kt
index e485003..5daa8c1 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedTest.kt
@@ -34,14 +34,14 @@
     fun constructor_success() {
         CreatePublicKeyCredentialRequestPrivileged(
             "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-            "RP", "ClientDataHash"
+            "RelyingParty", "ClientDataHash"
         )
     }
 
     @Test
     fun constructor_setsAllowHybridToTrueByDefault() {
         val createPublicKeyCredentialRequestPrivileged = CreatePublicKeyCredentialRequestPrivileged(
-            "JSON", "RP", "HASH"
+            "JSON", "RelyingParty", "HASH"
         )
         val allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid
         assertThat(allowHybridActual).isTrue()
@@ -52,7 +52,7 @@
         val allowHybridExpected = false
         val createPublicKeyCredentialRequestPrivileged = CreatePublicKeyCredentialRequestPrivileged(
             "testJson",
-            "RP", "Hash", allowHybridExpected
+            "RelyingParty", "Hash", allowHybridExpected
         )
         val allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid
         assertThat(allowHybridActual).isEqualTo(allowHybridExpected)
@@ -62,7 +62,7 @@
     fun builder_build_defaultAllowHybrid_true() {
         val defaultPrivilegedRequest = CreatePublicKeyCredentialRequestPrivileged.Builder(
             "{\"Data\":5}",
-            "RP", "HASH"
+            "RelyingParty", "HASH"
         ).build()
         assertThat(defaultPrivilegedRequest.allowHybrid).isTrue()
     }
@@ -73,7 +73,7 @@
         val createPublicKeyCredentialRequestPrivileged = CreatePublicKeyCredentialRequestPrivileged
             .Builder(
                 "testJson",
-                "RP", "Hash"
+                "RelyingParty", "Hash"
             ).setAllowHybrid(allowHybridExpected).build()
         val allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid
         assertThat(allowHybridActual).isEqualTo(allowHybridExpected)
@@ -83,21 +83,22 @@
     fun getter_requestJson_success() {
         val testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
         val createPublicKeyCredentialReqPriv =
-            CreatePublicKeyCredentialRequestPrivileged(testJsonExpected, "RP", "HASH")
+            CreatePublicKeyCredentialRequestPrivileged(testJsonExpected, "RelyingParty",
+                "HASH")
         val testJsonActual = createPublicKeyCredentialReqPriv.requestJson
         assertThat(testJsonActual).isEqualTo(testJsonExpected)
     }
 
     @Test
-    fun getter_rp_success() {
-        val testRpExpected = "RP"
+    fun getter_relyingParty_success() {
+        val testRelyingPartyExpected = "RelyingParty"
         val createPublicKeyCredentialRequestPrivileged =
             CreatePublicKeyCredentialRequestPrivileged(
                 "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                testRpExpected, "X342%4dfd7&"
+                testRelyingPartyExpected, "X342%4dfd7&"
             )
-        val testRpActual = createPublicKeyCredentialRequestPrivileged.rp
-        assertThat(testRpActual).isEqualTo(testRpExpected)
+        val testRelyingPartyActual = createPublicKeyCredentialRequestPrivileged.relyingParty
+        assertThat(testRelyingPartyActual).isEqualTo(testRelyingPartyExpected)
     }
 
     @Test
@@ -106,7 +107,7 @@
         val createPublicKeyCredentialRequestPrivileged =
             CreatePublicKeyCredentialRequestPrivileged(
                 "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                "RP", clientDataHashExpected
+                "RelyingParty", clientDataHashExpected
             )
         val clientDataHashActual = createPublicKeyCredentialRequestPrivileged.clientDataHash
         assertThat(clientDataHashActual).isEqualTo(clientDataHashExpected)
@@ -115,15 +116,16 @@
     @Test
     fun getter_frameworkProperties_success() {
         val requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
-        val rpExpected = "RP"
+        val relyingPartyExpected = "RelyingParty"
         val clientDataHashExpected = "X342%4dfd7&"
         val allowHybridExpected = false
         val expectedData = Bundle()
         expectedData.putString(
-            CreatePublicKeyCredentialBaseRequest.BUNDLE_KEY_REQUEST_JSON,
+            CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_REQUEST_JSON,
             requestJsonExpected
         )
-        expectedData.putString(CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_RP, rpExpected)
+        expectedData.putString(CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_RELYING_PARTY,
+            relyingPartyExpected)
         expectedData.putString(
             CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_CLIENT_DATA_HASH,
             clientDataHashExpected
@@ -135,7 +137,7 @@
 
         val request = CreatePublicKeyCredentialRequestPrivileged(
             requestJsonExpected,
-            rpExpected,
+            relyingPartyExpected,
             clientDataHashExpected,
             allowHybridExpected
         )
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt
index 32d4365..b757bb9 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt
@@ -17,8 +17,8 @@
 package androidx.credentials
 
 import android.os.Bundle
-import androidx.credentials.CreatePublicKeyCredentialBaseRequest.Companion.BUNDLE_KEY_REQUEST_JSON
 import androidx.credentials.CreatePublicKeyCredentialRequest.Companion.BUNDLE_KEY_ALLOW_HYBRID
+import androidx.credentials.CreatePublicKeyCredentialRequest.Companion.BUNDLE_KEY_REQUEST_JSON
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
index c8d6133..5f59d79 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
@@ -21,6 +21,9 @@
 import android.content.Context;
 
 import androidx.annotation.NonNull;
+import androidx.credentials.exceptions.ClearCredentialException;
+import androidx.credentials.exceptions.CreateCredentialException;
+import androidx.credentials.exceptions.GetCredentialException;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -50,9 +53,10 @@
                         null,
                         null,
                         Runnable::run,
-                        new CredentialManagerCallback<CreateCredentialResponse>() {
+                        new CredentialManagerCallback<CreateCredentialResponse,
+                                CreateCredentialException>() {
                             @Override
-                            public void onError(@NonNull CredentialManagerException e) {}
+                            public void onError(@NonNull CreateCredentialException e) {}
 
                             @Override
                             public void onResult(CreateCredentialResponse result) {}
@@ -70,9 +74,10 @@
                         null,
                         null,
                         Runnable::run,
-                        new CredentialManagerCallback<GetCredentialResponse>() {
+                        new CredentialManagerCallback<GetCredentialResponse,
+                                GetCredentialException>() {
                             @Override
-                            public void onError(@NonNull CredentialManagerException e) {}
+                            public void onError(@NonNull GetCredentialException e) {}
 
                             @Override
                             public void onResult(GetCredentialResponse result) {}
@@ -83,10 +88,18 @@
     @Test
     public void testClearCredentialSessionAsync() {
         assertThrows(UnsupportedOperationException.class,
-                () -> mCredentialManager.clearCredentialSessionAsync(
+                () -> mCredentialManager.clearCredentialStateAsync(
+                        new ClearCredentialStateRequest(),
                         null,
                         Runnable::run,
-                        result -> {})
+                        new CredentialManagerCallback<Void,
+                                ClearCredentialException>() {
+                            @Override
+                            public void onError(@NonNull ClearCredentialException e) {}
+
+                            @Override
+                            public void onResult(Void result) {}
+                        })
         );
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerTest.kt
index 4b9dcea..0525633 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerTest.kt
@@ -16,6 +16,9 @@
 
 package androidx.credentials
 
+import androidx.credentials.exceptions.ClearCredentialException
+import androidx.credentials.exceptions.CreateCredentialException
+import androidx.credentials.exceptions.GetCredentialException
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
@@ -59,7 +62,7 @@
     @Test
     fun testClearCredentialSession() = runBlocking<Unit> {
         assertThrows<UnsupportedOperationException> {
-            credentialManager.clearCredentialSession()
+            credentialManager.clearCredentialState(ClearCredentialStateRequest())
         }
     }
 
@@ -71,8 +74,10 @@
                 activity = null,
                 cancellationSignal = null,
                 executor = Runnable::run,
-                callback = object : CredentialManagerCallback<CreateCredentialResponse> {
+                callback = object : CredentialManagerCallback<CreateCredentialResponse,
+                    CreateCredentialException> {
                     override fun onResult(result: CreateCredentialResponse) {}
+                    override fun onError(e: CreateCredentialException) {}
                 }
             )
         }
@@ -88,8 +93,10 @@
                 activity = null,
                 cancellationSignal = null,
                 executor = Runnable::run,
-                callback = object : CredentialManagerCallback<GetCredentialResponse> {
+                callback = object : CredentialManagerCallback<GetCredentialResponse,
+                    GetCredentialException> {
                     override fun onResult(result: GetCredentialResponse) {}
+                    override fun onError(e: GetCredentialException) {}
                 }
             )
         }
@@ -98,11 +105,13 @@
     @Test
     fun testClearCredentialSessionAsync() {
         assertThrows<UnsupportedOperationException> {
-            credentialManager.clearCredentialSessionAsync(
+            credentialManager.clearCredentialStateAsync(
+                request = ClearCredentialStateRequest(),
                 cancellationSignal = null,
                 executor = Runnable::run,
-                callback = object : CredentialManagerCallback<Void> {
+                callback = object : CredentialManagerCallback<Void, ClearCredentialException> {
                     override fun onResult(result: Void) {}
+                    override fun onError(e: ClearCredentialException) {}
                 }
             )
         }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java
index 7575d2a..8508b5f 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java
@@ -16,8 +16,8 @@
 
 package androidx.credentials;
 
-import static androidx.credentials.GetPublicKeyCredentialBaseOption.BUNDLE_KEY_REQUEST_JSON;
 import static androidx.credentials.GetPublicKeyCredentialOption.BUNDLE_KEY_ALLOW_HYBRID;
+import static androidx.credentials.GetPublicKeyCredentialOption.BUNDLE_KEY_REQUEST_JSON;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -25,8 +25,15 @@
 
 import android.os.Bundle;
 
-import org.junit.Test;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
 public class GetPublicKeyCredentialOptionJavaTest {
 
     @Test
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedJavaTest.java
index 54efd71..f624b35 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedJavaTest.java
@@ -16,10 +16,10 @@
 
 package androidx.credentials;
 
-import static androidx.credentials.GetPublicKeyCredentialBaseOption.BUNDLE_KEY_REQUEST_JSON;
 import static androidx.credentials.GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_ALLOW_HYBRID;
 import static androidx.credentials.GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_CLIENT_DATA_HASH;
-import static androidx.credentials.GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_RP;
+import static androidx.credentials.GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_RELYING_PARTY;
+import static androidx.credentials.GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_REQUEST_JSON;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -43,14 +43,14 @@
     public void constructor_success() {
         new GetPublicKeyCredentialOptionPrivileged(
                         "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                        "RP", "ClientDataHash");
+                        "RelyingParty", "ClientDataHash");
     }
 
     @Test
     public void constructor_setsAllowHybridToTrueByDefault() {
         GetPublicKeyCredentialOptionPrivileged getPublicKeyCredentialOptionPrivileged =
                 new GetPublicKeyCredentialOptionPrivileged(
-                        "JSON", "RP", "HASH");
+                        "JSON", "RelyingParty", "HASH");
         boolean allowHybridActual = getPublicKeyCredentialOptionPrivileged.allowHybrid();
         assertThat(allowHybridActual).isTrue();
     }
@@ -60,7 +60,7 @@
         boolean allowHybridExpected = false;
         GetPublicKeyCredentialOptionPrivileged getPublicKeyCredentialOptionPrivileged =
                 new GetPublicKeyCredentialOptionPrivileged("testJson",
-                        "RP", "Hash", allowHybridExpected);
+                        "RelyingParty", "Hash", allowHybridExpected);
         boolean getAllowHybridActual = getPublicKeyCredentialOptionPrivileged.allowHybrid();
         assertThat(getAllowHybridActual).isEqualTo(allowHybridExpected);
     }
@@ -69,7 +69,7 @@
     public void builder_build_defaultAllowHybrid_success() {
         GetPublicKeyCredentialOptionPrivileged defaultPrivilegedRequest = new
                 GetPublicKeyCredentialOptionPrivileged.Builder("{\"Data\":5}",
-                "RP", "HASH").build();
+                "RelyingParty", "HASH").build();
         assertThat(defaultPrivilegedRequest.allowHybrid()).isTrue();
     }
 
@@ -78,7 +78,8 @@
         boolean allowHybridExpected = false;
         GetPublicKeyCredentialOptionPrivileged getPublicKeyCredentialOptionPrivileged =
                 new GetPublicKeyCredentialOptionPrivileged.Builder("testJson",
-                        "RP", "Hash").setAllowHybrid(allowHybridExpected).build();
+                        "RelyingParty", "Hash")
+                        .setAllowHybrid(allowHybridExpected).build();
         boolean getAllowHybridActual = getPublicKeyCredentialOptionPrivileged.allowHybrid();
         assertThat(getAllowHybridActual).isEqualTo(allowHybridExpected);
     }
@@ -87,20 +88,21 @@
     public void getter_requestJson_success() {
         String testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
         GetPublicKeyCredentialOptionPrivileged getPublicKeyCredentialOptionPrivileged =
-                new GetPublicKeyCredentialOptionPrivileged(testJsonExpected, "RP", "HASH");
+                new GetPublicKeyCredentialOptionPrivileged(testJsonExpected,
+                        "RelyingParty", "HASH");
         String testJsonActual = getPublicKeyCredentialOptionPrivileged.getRequestJson();
         assertThat(testJsonActual).isEqualTo(testJsonExpected);
     }
 
     @Test
-    public void getter_rp_success() {
-        String testRpExpected = "RP";
+    public void getter_relyingParty_success() {
+        String testRelyingPartyExpected = "RelyingParty";
         GetPublicKeyCredentialOptionPrivileged getPublicKeyCredentialOptionPrivileged =
                 new GetPublicKeyCredentialOptionPrivileged(
                         "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                        testRpExpected, "X342%4dfd7&");
-        String testRpActual = getPublicKeyCredentialOptionPrivileged.getRp();
-        assertThat(testRpActual).isEqualTo(testRpExpected);
+                        testRelyingPartyExpected, "X342%4dfd7&");
+        String testRelyingPartyActual = getPublicKeyCredentialOptionPrivileged.getRelyingParty();
+        assertThat(testRelyingPartyActual).isEqualTo(testRelyingPartyExpected);
     }
 
     @Test
@@ -109,7 +111,7 @@
         GetPublicKeyCredentialOptionPrivileged getPublicKeyCredentialOptionPrivileged =
                 new GetPublicKeyCredentialOptionPrivileged(
                         "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                        "RP", clientDataHashExpected);
+                        "RelyingParty", clientDataHashExpected);
         String clientDataHashActual = getPublicKeyCredentialOptionPrivileged.getClientDataHash();
         assertThat(clientDataHashActual).isEqualTo(clientDataHashExpected);
     }
@@ -117,18 +119,18 @@
     @Test
     public void getter_frameworkProperties_success() {
         String requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
-        String rpExpected = "RP";
+        String relyingPartyExpected = "RelyingParty";
         String clientDataHashExpected = "X342%4dfd7&";
         boolean allowHybridExpected = false;
         Bundle expectedData = new Bundle();
         expectedData.putString(BUNDLE_KEY_REQUEST_JSON, requestJsonExpected);
-        expectedData.putString(BUNDLE_KEY_RP, rpExpected);
+        expectedData.putString(BUNDLE_KEY_RELYING_PARTY, relyingPartyExpected);
         expectedData.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHashExpected);
         expectedData.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybridExpected);
 
         GetPublicKeyCredentialOptionPrivileged option =
                 new GetPublicKeyCredentialOptionPrivileged(
-                        requestJsonExpected, rpExpected, clientDataHashExpected,
+                        requestJsonExpected, relyingPartyExpected, clientDataHashExpected,
                         allowHybridExpected);
 
         assertThat(option.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedTest.kt
index e1c1079..c3ce0b1 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedTest.kt
@@ -34,14 +34,14 @@
     fun constructor_success() {
         GetPublicKeyCredentialOptionPrivileged(
             "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-            "RP", "ClientDataHash"
+            "RelyingParty", "ClientDataHash"
         )
     }
 
     @Test
     fun constructor_setsAllowHybridToTrueByDefault() {
         val getPublicKeyCredentialOptionPrivileged = GetPublicKeyCredentialOptionPrivileged(
-            "JSON", "RP", "HASH"
+            "JSON", "RelyingParty", "HASH"
         )
         val allowHybridActual = getPublicKeyCredentialOptionPrivileged.allowHybrid
         assertThat(allowHybridActual).isTrue()
@@ -51,7 +51,7 @@
     fun constructor_setsAllowHybridFalse() {
         val allowHybridExpected = false
         val getPublicKeyCredentialOptPriv = GetPublicKeyCredentialOptionPrivileged(
-            "JSON", "RP", "HASH", allowHybridExpected
+            "JSON", "RelyingParty", "HASH", allowHybridExpected
         )
         val getAllowHybridActual = getPublicKeyCredentialOptPriv.allowHybrid
         assertThat(getAllowHybridActual).isEqualTo(allowHybridExpected)
@@ -63,7 +63,7 @@
         val getPublicKeyCredentialOptionPrivileged = GetPublicKeyCredentialOptionPrivileged
             .Builder(
                 "testJson",
-                "RP", "Hash",
+                "RelyingParty", "Hash",
             ).setAllowHybrid(allowHybridExpected).build()
         val getAllowHybridActual = getPublicKeyCredentialOptionPrivileged.allowHybrid
         assertThat(getAllowHybridActual).isEqualTo(allowHybridExpected)
@@ -73,7 +73,7 @@
     fun builder_build_defaultAllowHybrid_true() {
         val defaultPrivilegedRequest = GetPublicKeyCredentialOptionPrivileged.Builder(
             "{\"Data\":5}",
-            "RP", "HASH"
+            "RelyingParty", "HASH"
         ).build()
         assertThat(defaultPrivilegedRequest.allowHybrid).isTrue()
     }
@@ -82,20 +82,21 @@
     fun getter_requestJson_success() {
         val testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
         val getPublicKeyCredentialOptionPrivileged =
-            GetPublicKeyCredentialOptionPrivileged(testJsonExpected, "RP", "HASH")
+            GetPublicKeyCredentialOptionPrivileged(testJsonExpected, "RelyingParty",
+                "HASH")
         val testJsonActual = getPublicKeyCredentialOptionPrivileged.requestJson
         assertThat(testJsonActual).isEqualTo(testJsonExpected)
     }
 
     @Test
-    fun getter_rp_success() {
-        val testRpExpected = "RP"
+    fun getter_relyingParty_success() {
+        val testRelyingPartyExpected = "RelyingParty"
         val getPublicKeyCredentialOptionPrivileged = GetPublicKeyCredentialOptionPrivileged(
             "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-            testRpExpected, "X342%4dfd7&"
+            testRelyingPartyExpected, "X342%4dfd7&"
         )
-        val testRpActual = getPublicKeyCredentialOptionPrivileged.rp
-        assertThat(testRpActual).isEqualTo(testRpExpected)
+        val testRelyingPartyActual = getPublicKeyCredentialOptionPrivileged.relyingParty
+        assertThat(testRelyingPartyActual).isEqualTo(testRelyingPartyExpected)
     }
 
     @Test
@@ -103,7 +104,7 @@
         val clientDataHashExpected = "X342%4dfd7&"
         val getPublicKeyCredentialOptionPrivileged = GetPublicKeyCredentialOptionPrivileged(
             "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-            "RP", clientDataHashExpected
+            "RelyingParty", clientDataHashExpected
         )
         val clientDataHashActual = getPublicKeyCredentialOptionPrivileged.clientDataHash
         assertThat(clientDataHashActual).isEqualTo(clientDataHashExpected)
@@ -112,15 +113,16 @@
     @Test
     fun getter_frameworkProperties_success() {
         val requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
-        val rpExpected = "RP"
+        val relyingPartyExpected = "RelyingParty"
         val clientDataHashExpected = "X342%4dfd7&"
         val allowHybridExpected = false
         val expectedData = Bundle()
         expectedData.putString(
-            GetPublicKeyCredentialBaseOption.BUNDLE_KEY_REQUEST_JSON,
+            GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_REQUEST_JSON,
             requestJsonExpected
         )
-        expectedData.putString(GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_RP, rpExpected)
+        expectedData.putString(GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_RELYING_PARTY,
+            relyingPartyExpected)
         expectedData.putString(
             GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_CLIENT_DATA_HASH,
             clientDataHashExpected
@@ -131,7 +133,7 @@
         )
 
         val option = GetPublicKeyCredentialOptionPrivileged(
-            requestJsonExpected, rpExpected, clientDataHashExpected,
+            requestJsonExpected, relyingPartyExpected, clientDataHashExpected,
             allowHybridExpected
         )
 
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
index 821b580..4b9bbd8 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
@@ -76,7 +76,7 @@
         val allowHybridExpected = false
         val expectedData = Bundle()
         expectedData.putString(
-            GetPublicKeyCredentialBaseOption.BUNDLE_KEY_REQUEST_JSON,
+            GetPublicKeyCredentialOption.BUNDLE_KEY_REQUEST_JSON,
             requestJsonExpected
         )
         expectedData.putBoolean(
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialExceptionJavaTest.java
new file mode 100644
index 0000000..0c8c69b
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialExceptionJavaTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ClearCredentialExceptionJavaTest {
+
+    @Test(expected = ClearCredentialException.class)
+    public void construct_inputsNonEmpty_success() throws ClearCredentialException {
+        throw new ClearCredentialException("type", "msg");
+    }
+
+    @Test(expected = ClearCredentialException.class)
+    public void construct_errorMessageNull_success() throws ClearCredentialException {
+        throw new ClearCredentialException("type", null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void construct_typeEmpty_throws() throws ClearCredentialException {
+        throw new ClearCredentialException("", "msg");
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void construct_typeNull_throws() throws ClearCredentialException {
+        throw new ClearCredentialException(null, "msg");
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialExceptionTest.kt
new file mode 100644
index 0000000..ef3a11a
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialExceptionTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ClearCredentialExceptionTest {
+    @Test(expected = ClearCredentialException::class)
+    fun construct_inputsNonEmpty_success() {
+        throw ClearCredentialException("type", "msg")
+    }
+
+    @Test(expected = ClearCredentialException::class)
+    fun construct_errorMessageNull_success() {
+        throw ClearCredentialException("type", null)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun construct_typeEmpty_throws() {
+        throw ClearCredentialException("", "msg")
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialInterruptedExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialInterruptedExceptionJavaTest.java
new file mode 100644
index 0000000..5854464
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialInterruptedExceptionJavaTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ClearCredentialInterruptedExceptionJavaTest {
+    @Test(expected = ClearCredentialInterruptedException.class)
+    public void construct_inputNonEmpty_success() throws ClearCredentialInterruptedException {
+        throw new ClearCredentialInterruptedException("msg");
+    }
+
+    @Test(expected = ClearCredentialInterruptedException.class)
+    public void construct_errorMessageNull_success() throws ClearCredentialInterruptedException {
+        throw new ClearCredentialInterruptedException(null);
+    }
+
+    @Test
+    public void getter_type_success() {
+        ClearCredentialInterruptedException exception = new
+                ClearCredentialInterruptedException("msg");
+        String expectedType = ClearCredentialInterruptedException
+                .TYPE_CLEAR_CREDENTIAL_INTERRUPTED_EXCEPTION;
+        Truth.assertThat(exception.getType()).isEqualTo(expectedType);
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialInterruptedExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialInterruptedExceptionTest.kt
new file mode 100644
index 0000000..fcb53ef
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialInterruptedExceptionTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ClearCredentialInterruptedExceptionTest {
+    @Test(expected = ClearCredentialInterruptedException::class)
+    fun construct_inputNonEmpty_success() {
+        throw ClearCredentialInterruptedException("msg")
+    }
+
+    @Test(expected = ClearCredentialInterruptedException::class)
+    fun construct_errorMessageNull_success() {
+        throw ClearCredentialInterruptedException(null)
+    }
+
+    @Test
+    fun getter_type_success() {
+        val exception = ClearCredentialInterruptedException("msg")
+        val expectedType =
+            ClearCredentialInterruptedException.TYPE_CLEAR_CREDENTIAL_INTERRUPTED_EXCEPTION
+        Truth.assertThat(exception.type).isEqualTo(expectedType)
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialUnknownExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialUnknownExceptionJavaTest.java
new file mode 100644
index 0000000..8eeba42
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialUnknownExceptionJavaTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ClearCredentialUnknownExceptionJavaTest {
+    @Test(expected = ClearCredentialUnknownException.class)
+    public void construct_inputNonEmpty_success() throws ClearCredentialUnknownException {
+        throw new ClearCredentialUnknownException("msg");
+    }
+
+    @Test(expected = ClearCredentialUnknownException.class)
+    public void construct_errorMessageNull_success() throws ClearCredentialUnknownException {
+        throw new ClearCredentialUnknownException(null);
+    }
+
+    @Test
+    public void getter_type_success() {
+        ClearCredentialUnknownException exception = new
+                ClearCredentialUnknownException("msg");
+        String expectedType = ClearCredentialUnknownException
+                .TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION;
+        Truth.assertThat(exception.getType()).isEqualTo(expectedType);
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialUnknownExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialUnknownExceptionTest.kt
new file mode 100644
index 0000000..34d15fe
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/ClearCredentialUnknownExceptionTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ClearCredentialUnknownExceptionTest {
+    @Test(expected = ClearCredentialUnknownException::class)
+    fun construct_inputNonEmpty_success() {
+        throw ClearCredentialUnknownException("msg")
+    }
+
+    @Test(expected = ClearCredentialUnknownException::class)
+    fun construct_errorMessageNull_success() {
+        throw ClearCredentialUnknownException(null)
+    }
+
+    @Test
+    fun getter_type_success() {
+        val exception = ClearCredentialUnknownException("msg")
+        val expectedType = ClearCredentialUnknownException.TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION
+        Truth.assertThat(exception.type).isEqualTo(expectedType)
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialCanceledExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialCanceledExceptionJavaTest.java
new file mode 100644
index 0000000..9dc4526
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialCanceledExceptionJavaTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CreateCredentialCanceledExceptionJavaTest {
+    @Test(expected = CreateCredentialCanceledException.class)
+    public void construct_inputNonEmpty_success() throws CreateCredentialCanceledException {
+        throw new CreateCredentialCanceledException("msg");
+    }
+
+    @Test(expected = CreateCredentialCanceledException.class)
+    public void construct_errorMessageNull_success() throws CreateCredentialCanceledException {
+        throw new CreateCredentialCanceledException(null);
+    }
+
+    @Test
+    public void getter_type_success() {
+        CreateCredentialCanceledException exception = new CreateCredentialCanceledException("msg");
+        String expectedType =
+                CreateCredentialCanceledException.TYPE_CREATE_CREDENTIAL_CANCELED_EXCEPTION;
+        Truth.assertThat(exception.getType()).isEqualTo(expectedType);
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialCanceledExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialCanceledExceptionTest.kt
new file mode 100644
index 0000000..903bc23
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialCanceledExceptionTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CreateCredentialCanceledExceptionTest {
+    @Test(expected = CreateCredentialCanceledException::class)
+    fun construct_inputNonEmpty_success() {
+        throw CreateCredentialCanceledException("msg")
+    }
+
+    @Test(expected = CreateCredentialCanceledException::class)
+    fun construct_errorMessageNull_success() {
+        throw CreateCredentialCanceledException(null)
+    }
+
+    @Test
+    fun getter_type_success() {
+        val exception = CreateCredentialCanceledException("msg")
+        val expectedType = CreateCredentialCanceledException
+            .TYPE_CREATE_CREDENTIAL_CANCELED_EXCEPTION
+        Truth.assertThat(exception.type).isEqualTo(expectedType)
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialExceptionJavaTest.java
new file mode 100644
index 0000000..6085e51
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialExceptionJavaTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CreateCredentialExceptionJavaTest {
+    @Test(expected = CreateCredentialException.class)
+    public void construct_inputsNonEmpty_success() throws CreateCredentialException {
+        throw new CreateCredentialException("type", "msg");
+    }
+
+    @Test(expected = CreateCredentialException.class)
+    public void construct_errorMessageNull_success() throws CreateCredentialException {
+        throw new CreateCredentialException("type", null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void construct_typeEmpty_throws() throws CreateCredentialException {
+        throw new CreateCredentialException("", "msg");
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void construct_typeNull_throws() throws CreateCredentialException {
+        throw new CreateCredentialException(null, "msg");
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialExceptionTest.kt
new file mode 100644
index 0000000..7141742
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialExceptionTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CreateCredentialExceptionTest {
+    @Test(expected = CreateCredentialException::class)
+    fun construct_inputsNonEmpty_success() {
+        throw CreateCredentialException("type", "msg")
+    }
+
+    @Test(expected = CreateCredentialException::class)
+    fun construct_errorMessageNull_success() {
+        throw CreateCredentialException("type", null)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun construct_typeEmpty_throws() {
+        throw CreateCredentialException("", "msg")
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialInterruptedExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialInterruptedExceptionJavaTest.java
new file mode 100644
index 0000000..93ef3bb
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialInterruptedExceptionJavaTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CreateCredentialInterruptedExceptionJavaTest {
+    @Test(expected = CreateCredentialInterruptedException.class)
+    public void construct_inputNonEmpty_success() throws CreateCredentialInterruptedException {
+        throw new CreateCredentialInterruptedException("msg");
+    }
+
+    @Test(expected = CreateCredentialInterruptedException.class)
+    public void construct_errorMessageNull_success() throws CreateCredentialInterruptedException {
+        throw new CreateCredentialInterruptedException(null);
+    }
+
+    @Test
+    public void getter_type_success() {
+        CreateCredentialInterruptedException exception = new
+                CreateCredentialInterruptedException("msg");
+        String expectedType = CreateCredentialInterruptedException
+                .TYPE_CREATE_CREDENTIAL_INTERRUPTED_EXCEPTION;
+        Truth.assertThat(exception.getType()).isEqualTo(expectedType);
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialInterruptedExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialInterruptedExceptionTest.kt
new file mode 100644
index 0000000..e32a750
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialInterruptedExceptionTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CreateCredentialInterruptedExceptionTest {
+    @Test(expected = CreateCredentialInterruptedException::class)
+    fun construct_inputNonEmpty_success() {
+        throw CreateCredentialInterruptedException("msg")
+    }
+
+    @Test(expected = CreateCredentialInterruptedException::class)
+    fun construct_errorMessageNull_success() {
+        throw CreateCredentialInterruptedException(null)
+    }
+
+    @Test
+    fun getter_type_success() {
+        val exception = CreateCredentialInterruptedException("msg")
+        val expectedType =
+            CreateCredentialInterruptedException.TYPE_CREATE_CREDENTIAL_INTERRUPTED_EXCEPTION
+        Truth.assertThat(exception.type).isEqualTo(expectedType)
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialUnknownExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialUnknownExceptionJavaTest.java
new file mode 100644
index 0000000..100c4cf
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialUnknownExceptionJavaTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CreateCredentialUnknownExceptionJavaTest {
+    @Test(expected = CreateCredentialUnknownException.class)
+    public void construct_inputNonEmpty_success() throws CreateCredentialUnknownException {
+        throw new CreateCredentialUnknownException("msg");
+    }
+
+    @Test(expected = CreateCredentialUnknownException.class)
+    public void construct_errorMessageNull_success() throws CreateCredentialUnknownException {
+        throw new CreateCredentialUnknownException(null);
+    }
+
+    @Test
+    public void getter_type_success() {
+        CreateCredentialUnknownException exception = new
+                CreateCredentialUnknownException("msg");
+        String expectedType = CreateCredentialUnknownException
+                .TYPE_CREATE_CREDENTIAL_UNKNOWN_EXCEPTION;
+        Truth.assertThat(exception.getType()).isEqualTo(expectedType);
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialUnknownExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialUnknownExceptionTest.kt
new file mode 100644
index 0000000..38ab6f8
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/CreateCredentialUnknownExceptionTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CreateCredentialUnknownExceptionTest {
+    @Test(expected = CreateCredentialUnknownException::class)
+    fun construct_inputNonEmpty_success() {
+        throw CreateCredentialUnknownException("msg")
+    }
+
+    @Test(expected = CreateCredentialUnknownException::class)
+    fun construct_errorMessageNull_success() {
+        throw CreateCredentialUnknownException(null)
+    }
+
+    @Test
+    fun getter_type_success() {
+        val exception = CreateCredentialUnknownException("msg")
+        val expectedType = CreateCredentialUnknownException.TYPE_CREATE_CREDENTIAL_UNKNOWN_EXCEPTION
+        Truth.assertThat(exception.type).isEqualTo(expectedType)
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialCanceledExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialCanceledExceptionJavaTest.java
new file mode 100644
index 0000000..e5cf2f3
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialCanceledExceptionJavaTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GetCredentialCanceledExceptionJavaTest {
+    @Test(expected = GetCredentialCanceledException.class)
+    public void construct_inputNonEmpty_success() throws GetCredentialCanceledException {
+        throw new GetCredentialCanceledException("msg");
+    }
+
+    @Test(expected = GetCredentialCanceledException.class)
+    public void construct_errorMessageNull_success() throws GetCredentialCanceledException {
+        throw new GetCredentialCanceledException(null);
+    }
+
+    @Test
+    public void getter_type_success() {
+        GetCredentialCanceledException exception = new GetCredentialCanceledException("msg");
+        String expectedType = GetCredentialCanceledException.TYPE_GET_CREDENTIAL_CANCELED_EXCEPTION;
+        Truth.assertThat(exception.getType()).isEqualTo(expectedType);
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialCanceledExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialCanceledExceptionTest.kt
new file mode 100644
index 0000000..61e7c3c
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialCanceledExceptionTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GetCredentialCanceledExceptionTest {
+    @Test(expected = GetCredentialCanceledException::class)
+    fun construct_inputNonEmpty_success() {
+        throw GetCredentialCanceledException("msg")
+    }
+
+    @Test(expected = GetCredentialCanceledException::class)
+    fun construct_errorMessageNull_success() {
+        throw GetCredentialCanceledException(null)
+    }
+
+    @Test
+    fun getter_type_success() {
+        val exception = GetCredentialCanceledException("msg")
+        val expectedType = GetCredentialCanceledException.TYPE_GET_CREDENTIAL_CANCELED_EXCEPTION
+        Truth.assertThat(exception.type).isEqualTo(expectedType)
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialExceptionJavaTest.java
new file mode 100644
index 0000000..048e3a4
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialExceptionJavaTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GetCredentialExceptionJavaTest {
+    @Test(expected = GetCredentialException.class)
+    public void construct_inputsNonEmpty_success() throws GetCredentialException {
+        throw new GetCredentialException("type", "msg");
+    }
+
+    @Test(expected = GetCredentialException.class)
+    public void construct_errorMessageNull_success() throws GetCredentialException {
+        throw new GetCredentialException("type", null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void construct_typeEmpty_throws() throws GetCredentialException {
+        throw new GetCredentialException("", "msg");
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void construct_typeNull_throws() throws GetCredentialException {
+        throw new GetCredentialException(null, "msg");
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialExceptionTest.kt
new file mode 100644
index 0000000..b70ddd0
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialExceptionTest.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GetCredentialExceptionTest {
+    @Test(expected = GetCredentialException::class)
+    fun construct_inputsNonEmpty_success() {
+        throw GetCredentialException("type", "msg")
+    }
+
+    @Test(expected = GetCredentialException::class)
+    fun construct_errorMessageNull_success() {
+        throw GetCredentialException("type", null)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun construct_typeEmpty_throws() {
+        throw GetCredentialException("", "msg")
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialInterruptedExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialInterruptedExceptionJavaTest.java
new file mode 100644
index 0000000..7e2ae31
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialInterruptedExceptionJavaTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GetCredentialInterruptedExceptionJavaTest {
+    @Test(expected = GetCredentialInterruptedException.class)
+    public void construct_inputNonEmpty_success() throws GetCredentialInterruptedException {
+        throw new GetCredentialInterruptedException("msg");
+    }
+
+    @Test(expected = GetCredentialInterruptedException.class)
+    public void construct_errorMessageNull_success() throws GetCredentialInterruptedException {
+        throw new GetCredentialInterruptedException(null);
+    }
+
+    @Test
+    public void getter_type_success() {
+        GetCredentialInterruptedException exception = new
+                GetCredentialInterruptedException("msg");
+        String expectedType = GetCredentialInterruptedException
+                .TYPE_GET_CREDENTIAL_INTERRUPTED_EXCEPTION;
+        Truth.assertThat(exception.getType()).isEqualTo(expectedType);
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialInterruptedExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialInterruptedExceptionTest.kt
new file mode 100644
index 0000000..809d0a8
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialInterruptedExceptionTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GetCredentialInterruptedExceptionTest {
+    @Test(expected = GetCredentialInterruptedException::class)
+    fun construct_inputNonEmpty_success() {
+        throw GetCredentialInterruptedException("msg")
+    }
+
+    @Test(expected = GetCredentialInterruptedException::class)
+    fun construct_errorMessageNull_success() {
+        throw GetCredentialInterruptedException(null)
+    }
+
+    @Test
+    fun getter_type_success() {
+        val exception = GetCredentialInterruptedException("msg")
+        val expectedType = GetCredentialInterruptedException
+            .TYPE_GET_CREDENTIAL_INTERRUPTED_EXCEPTION
+        Truth.assertThat(exception.type).isEqualTo(expectedType)
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialUnknownExceptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialUnknownExceptionJavaTest.java
new file mode 100644
index 0000000..f4d53ad
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialUnknownExceptionJavaTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GetCredentialUnknownExceptionJavaTest {
+    @Test(expected = GetCredentialUnknownException.class)
+    public void construct_inputNonEmpty_success() throws GetCredentialUnknownException {
+        throw new GetCredentialUnknownException("msg");
+    }
+
+    @Test(expected = GetCredentialUnknownException.class)
+    public void construct_errorMessageNull_success() throws GetCredentialUnknownException {
+        throw new GetCredentialUnknownException(null);
+    }
+
+    @Test
+    public void getter_type_success() {
+        GetCredentialUnknownException exception = new
+                GetCredentialUnknownException("msg");
+        String expectedType = GetCredentialUnknownException
+                .TYPE_GET_CREDENTIAL_UNKNOWN_EXCEPTION;
+        Truth.assertThat(exception.getType()).isEqualTo(expectedType);
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialUnknownExceptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialUnknownExceptionTest.kt
new file mode 100644
index 0000000..60ff2e4
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/exceptions/GetCredentialUnknownExceptionTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GetCredentialUnknownExceptionTest {
+    @Test(expected = GetCredentialUnknownException::class)
+    fun construct_inputNonEmpty_success() {
+        throw GetCredentialUnknownException("msg")
+    }
+
+    @Test(expected = GetCredentialUnknownException::class)
+    fun construct_errorMessageNull_success() {
+        throw GetCredentialUnknownException(null)
+    }
+
+    @Test
+    fun getter_type_success() {
+        val exception = GetCredentialUnknownException("msg")
+        val expectedType = GetCredentialUnknownException.TYPE_GET_CREDENTIAL_UNKNOWN_EXCEPTION
+        Truth.assertThat(exception.type).isEqualTo(expectedType)
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialManagerException.kt b/credentials/credentials/src/main/java/androidx/credentials/ClearCredentialStateRequest.kt
similarity index 62%
rename from credentials/credentials/src/main/java/androidx/credentials/CredentialManagerException.kt
rename to credentials/credentials/src/main/java/androidx/credentials/ClearCredentialStateRequest.kt
index 74b3702..b8d3296 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialManagerException.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/ClearCredentialStateRequest.kt
@@ -17,14 +17,6 @@
 package androidx.credentials
 
 /**
- * Represents an error encountered during a Credential Manager flow.
- *
- * @param errorCode an integer representing the type of the error
- * @param errorMessage a human-readable string that describes the error
+ * Request class for clearing a user's credential state from the credential providers.
  */
-class CredentialManagerException(
-    val errorCode: Int,
-    val errorMessage: CharSequence? = null,
-) : Exception(errorMessage?.toString()) {
-    // TODO: add specific errors.
-}
\ No newline at end of file
+class ClearCredentialStateRequest
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
index 7c373fe..22b83923 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
@@ -17,6 +17,7 @@
 package androidx.credentials
 
 import android.os.Bundle
+import androidx.annotation.RestrictTo
 
 /**
  * Base request class for registering a credential.
@@ -24,14 +25,15 @@
  * An application can construct a subtype request and call [CredentialManager.executeCreateCredential] to
  * launch framework UI flows to collect consent and any other metadata needed from the user to
  * register a new user credential.
- *
- * @property type the credential type determined by the credential-type-specific subclass
- * @property data the request data in the [Bundle] format
- * @property requireSystemProvider true if must only be fulfilled by a system provider and false
- *                              otherwise
  */
 open class CreateCredentialRequest(
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     val type: String,
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     val data: Bundle,
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     val requireSystemProvider: Boolean,
 )
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialResponse.kt b/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialResponse.kt
index 5267997..268cac7 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialResponse.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialResponse.kt
@@ -17,12 +17,17 @@
 package androidx.credentials
 
 import android.os.Bundle
+import androidx.annotation.RestrictTo
 
 /**
  * Base response class for the credential creation operation made with the
  * [CreateCredentialRequest].
- *
- * @property type the credential type determined by the credential-type-specific subclass
- * @property data the response data in the [Bundle] format
  */
-open class CreateCredentialResponse(val type: String, val data: Bundle)
\ No newline at end of file
+open class CreateCredentialResponse(
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    val type: String,
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    val data: Bundle
+    )
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialBaseRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialBaseRequest.kt
deleted file mode 100644
index 7fbe48a..0000000
--- a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialBaseRequest.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.credentials
-
-import android.os.Bundle
-import androidx.annotation.VisibleForTesting
-
-/**
- * Base request class for registering a public key credential.
- *
- * An application can construct a subtype request and call [CredentialManager.executeCreateCredential] to
- * launch framework UI flows to collect consent and any other metadata needed from the user to
- * register a new user credential.
- *
- * @property requestJson The request in JSON format
- * @throws NullPointerException If [requestJson] is null. This is handled by the Kotlin runtime
- * @throws IllegalArgumentException If [requestJson] is empty
- *
- * @hide
- */
-abstract class CreatePublicKeyCredentialBaseRequest constructor(
-    val requestJson: String,
-    type: String,
-    data: Bundle,
-    requireSystemProvider: Boolean,
-) : CreateCredentialRequest(type, data, requireSystemProvider) {
-
-    init {
-        require(requestJson.isNotEmpty()) { "request json must not be empty" }
-    }
-
-    /** @hide */
-    companion object {
-        @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
-        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
-    }
-}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt
index fe79aa99..b0862a5 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt
@@ -22,29 +22,34 @@
 /**
  * A request to register a passkey from the user's public key credential provider.
  *
- * @property requestJson the request in JSON format
+ * @property requestJson the privileged request in JSON format in the standard webauthn web json
+ * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
  * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default
- * @throws NullPointerException If [requestJson] or [allowHybrid] is null. This is handled by the
- * Kotlin runtime
+ * true by default, with hybrid credentials defined
+ * [here](https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid)
+ * @throws NullPointerException If [requestJson] is null
  * @throws IllegalArgumentException If [requestJson] is empty
- *
- * @hide
  */
 class CreatePublicKeyCredentialRequest @JvmOverloads constructor(
-    requestJson: String,
+    val requestJson: String,
     @get:JvmName("allowHybrid")
     val allowHybrid: Boolean = true
-) : CreatePublicKeyCredentialBaseRequest(
-    requestJson,
+) : CreateCredentialRequest(
     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
     toBundle(requestJson, allowHybrid),
     false,
 ) {
+
+    init {
+        require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
+    }
+
     /** @hide */
     companion object {
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
         const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
 
         @JvmStatic
         internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle {
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivileged.kt b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivileged.kt
index 1641f43..323eccb 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivileged.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivileged.kt
@@ -22,39 +22,45 @@
 /**
  * A privileged request to register a passkey from the user’s public key credential provider, where
  * the caller can modify the rp. Only callers with privileged permission, e.g. user’s default
- * brower, caBLE, can use this.
+ * brower, caBLE, can use this. These permissions will be introduced in an upcoming release.
+ * TODO("Add specific permission info/annotation")
  *
- * @property requestJson the privileged request in JSON format
+ * @property requestJson the privileged request in JSON format in the standard webauthn web json
+ * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
  * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default
- * @property rp the expected true RP ID which will override the one in the [requestJson]
- * @property clientDataHash a hash that is used to verify the [rp] Identity
- * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash] is
- * null. This is handled by the Kotlin runtime
- * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty
- *
- * @hide
+ * true by default, with hybrid credentials defined
+ * [here](https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid)
+ * @property relyingParty the expected true RP ID which will override the one in the [requestJson], where
+ * rp is defined [here](https://w3c.github.io/webauthn/#rp-id)
+ * @property clientDataHash a hash that is used to verify the [relyingParty] Identity
+ * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash] is
+ * null
+ * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is empty
  */
 class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor(
-    requestJson: String,
-    val rp: String,
+    val requestJson: String,
+    val relyingParty: String,
     val clientDataHash: String,
     @get:JvmName("allowHybrid")
     val allowHybrid: Boolean = true
-) : CreatePublicKeyCredentialBaseRequest(
-    requestJson,
+) : CreateCredentialRequest(
     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-    toBundle(requestJson, rp, clientDataHash, allowHybrid),
+    toBundle(requestJson, relyingParty, clientDataHash, allowHybrid),
     false,
 ) {
 
     init {
-        require(rp.isNotEmpty()) { "rp must not be empty" }
+        require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
+        require(relyingParty.isNotEmpty()) { "rp must not be empty" }
         require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" }
     }
 
     /** A builder for [CreatePublicKeyCredentialRequestPrivileged]. */
-    class Builder(var requestJson: String, var rp: String, var clientDataHash: String) {
+    class Builder(
+        private var requestJson: String,
+        private var relyingParty: String,
+        private var clientDataHash: String
+        ) {
 
         private var allowHybrid: Boolean = true
 
@@ -69,6 +75,7 @@
         /**
          * Sets whether hybrid credentials are allowed to fulfill this request, true by default.
          */
+        @Suppress("MissingGetterMatchingBuilder")
         fun setAllowHybrid(allowHybrid: Boolean): Builder {
             this.allowHybrid = allowHybrid
             return this
@@ -77,13 +84,13 @@
         /**
          * Sets the expected true RP ID which will override the one in the [requestJson].
          */
-        fun setRp(rp: String): Builder {
-            this.rp = rp
+        fun setRelyingParty(relyingParty: String): Builder {
+            this.relyingParty = relyingParty
             return this
         }
 
         /**
-         * Sets a hash that is used to verify the [rp] Identity.
+         * Sets a hash that is used to verify the [relyingParty] Identity.
          */
         fun setClientDataHash(clientDataHash: String): Builder {
             this.clientDataHash = clientDataHash
@@ -93,30 +100,32 @@
         /** Builds a [CreatePublicKeyCredentialRequestPrivileged]. */
         fun build(): CreatePublicKeyCredentialRequestPrivileged {
             return CreatePublicKeyCredentialRequestPrivileged(this.requestJson,
-                this.rp, this.clientDataHash, this.allowHybrid)
+                this.relyingParty, this.clientDataHash, this.allowHybrid)
         }
     }
 
     /** @hide */
     companion object {
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP"
+        const val BUNDLE_KEY_RELYING_PARTY = "androidx.credentials.BUNDLE_KEY_RELYING_PARTY"
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
         const val BUNDLE_KEY_CLIENT_DATA_HASH =
             "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
         const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
 
         @JvmStatic
         internal fun toBundle(
             requestJson: String,
-            rp: String,
+            relyingParty: String,
             clientDataHash: String,
             allowHybrid: Boolean
         ): Bundle {
             val bundle = Bundle()
             bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
-            bundle.putString(BUNDLE_KEY_RP, rp)
+            bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty)
             bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
             bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
             return bundle
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialResponse.kt b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialResponse.kt
index 7b5cd1d..7108e02 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialResponse.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialResponse.kt
@@ -23,11 +23,8 @@
  * A response of a public key credential (passkey) flow.
  *
  * @property registrationResponseJson the public key credential registration response in JSON format
- * @throws NullPointerException If [registrationResponseJson] is null. This is handled by the Kotlin
- * runtime
+ * @throws NullPointerException If [registrationResponseJson] is null
  * @throws IllegalArgumentException If [registrationResponseJson] is blank
- *
- * @hide
  */
 class CreatePublicKeyCredentialResponse(
     val registrationResponseJson: String
diff --git a/credentials/credentials/src/main/java/androidx/credentials/Credential.kt b/credentials/credentials/src/main/java/androidx/credentials/Credential.kt
index d1d9629..7ae769a5 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/Credential.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/Credential.kt
@@ -17,11 +17,16 @@
 package androidx.credentials
 
 import android.os.Bundle
+import androidx.annotation.RestrictTo
 
 /**
  * Base class for a credential with which the user consented to authenticate to the app.
- *
- * @property type the credential type determined by the credential-type-specific subclass
- * @property data the credential data in the [Bundle] format.
  */
-open class Credential(val type: String, val data: Bundle)
\ No newline at end of file
+open class Credential(
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    val type: String,
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    val data: Bundle
+    )
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialManager.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialManager.kt
index 8023aa4..f97d933 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialManager.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialManager.kt
@@ -19,6 +19,9 @@
 import android.app.Activity
 import android.content.Context
 import android.os.CancellationSignal
+import androidx.credentials.exceptions.ClearCredentialException
+import androidx.credentials.exceptions.CreateCredentialException
+import androidx.credentials.exceptions.GetCredentialException
 import java.util.concurrent.Executor
 import kotlin.coroutines.resume
 import kotlin.coroutines.resumeWithException
@@ -30,6 +33,54 @@
  * An application can call the CredentialManager apis to launch framework UI flows for a user to
  * register a new credential or to consent to a saved credential from supported credential
  * providers, which can then be used to authenticate to the app.
+ *
+ * This class contains its own exception types.
+ * They represent unique failures during the Credential Manager flow. As required, they
+ * can be extended for unique types containing new and unique versions of the exception - either
+ * with new 'exception types' (same credential class, different exceptions), or inner subclasses
+ * and their exception types (a subclass credential class and all their exception types).
+ *
+ * For example, if there is an UNKNOWN exception type, assuming the base Exception is
+ * [ClearCredentialException], we can add an 'exception type' class for it as follows:
+ * TODO("Add in new flow with extensive 'getType' function")
+ * ```
+ * class ClearCredentialUnknownException(
+ *     errorMessage: CharSequence? = null
+ * ) : ClearCredentialException(TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION, errorMessage) {
+ *  // ...Any required impl here...//
+ *  companion object {
+ *       private const val TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION: String =
+ *       "androidx.credentials.TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION"
+ *   }
+ * }
+ * ```
+ *
+ * Furthermore, the base class can be subclassed to a new more specific credential type, which
+ * then can further be subclassed into individual exception types. The first is an example of a
+ * 'inner credential type exception', and the next is a 'exception type' of this subclass exception.
+ *
+ * ```
+ * class UniqueCredentialBasedOnClearCredentialException(
+ *     type: String,
+ *     errorMessage: CharSequence? = null
+ * ) : ClearCredentialException(type, errorMessage) {
+ *  // ... Any required impl here...//
+ * }
+ * // .... code and logic .... //
+ * class UniqueCredentialBasedOnClearCredentialUnknownException(
+ *     errorMessage: CharSequence? = null
+ * ) : ClearCredentialException(TYPE_UNIQUE_CREDENTIAL_BASED_ON_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION,
+ * errorMessage) {
+ * // ... Any required impl here ... //
+ *  companion object {
+ *       private const val
+ *       TYPE_UNIQUE_CREDENTIAL_BASED_ON_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION: String =
+ *       "androidx.credentials.TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION"
+ *   }
+ * }
+ * ```
+ *
+ *
  */
 @Suppress("UNUSED_PARAMETER")
 class CredentialManager private constructor(private val context: Context) {
@@ -61,12 +112,13 @@
         val canceller = CancellationSignal()
         continuation.invokeOnCancellation { canceller.cancel() }
 
-        val callback = object : CredentialManagerCallback<GetCredentialResponse> {
+        val callback = object : CredentialManagerCallback<GetCredentialResponse,
+            GetCredentialException> {
             override fun onResult(result: GetCredentialResponse) {
                 continuation.resume(result)
             }
 
-            override fun onError(e: CredentialManagerException) {
+            override fun onError(e: GetCredentialException) {
                 continuation.resumeWithException(e)
             }
         }
@@ -104,12 +156,13 @@
         val canceller = CancellationSignal()
         continuation.invokeOnCancellation { canceller.cancel() }
 
-        val callback = object : CredentialManagerCallback<CreateCredentialResponse> {
+        val callback = object : CredentialManagerCallback<CreateCredentialResponse,
+            CreateCredentialException> {
             override fun onResult(result: CreateCredentialResponse) {
                 continuation.resume(result)
             }
 
-            override fun onError(e: CredentialManagerException) {
+            override fun onError(e: CreateCredentialException) {
                 continuation.resumeWithException(e)
             }
         }
@@ -125,30 +178,39 @@
     }
 
     /**
-     * Clears the current user credential session from all credential providers.
+     * Clears the current user credential state from all credential providers.
      *
-     * Usually invoked after your user signs out of your app so that they will not be
-     * automatically signed in the next time.
+     * You should invoked this api after your user signs out of your app to notify all credential
+     * providers that any stored credential session for the given app should be cleared.
      *
-     * @hide
+     * A credential provider may have stored an active credential session and use it to limit
+     * sign-in options for future get-credential calls. For example, it may prioritize the active
+     * credential over any other available credential. When your user explicitly signs out of your
+     * app and in order to get the holistic sign-in options the next time, you should call this API
+     * to let the provider clear any stored credential session.
+     *
+     * @param request the request for clearing the app user's credential state
      */
-    suspend fun clearCredentialSession(): Unit = suspendCancellableCoroutine { continuation ->
+    suspend fun clearCredentialState(
+        request: ClearCredentialStateRequest
+    ): Unit = suspendCancellableCoroutine { continuation ->
         // Any Android API that supports cancellation should be configured to propagate
         // coroutine cancellation as follows:
         val canceller = CancellationSignal()
         continuation.invokeOnCancellation { canceller.cancel() }
 
-        val callback = object : CredentialManagerCallback<Void> {
+        val callback = object : CredentialManagerCallback<Void, ClearCredentialException> {
             override fun onResult(result: Void) {
                 continuation.resume(Unit)
             }
 
-            override fun onError(e: CredentialManagerException) {
+            override fun onError(e: ClearCredentialException) {
                 continuation.resumeWithException(e)
             }
         }
 
-        clearCredentialSessionAsync(
+        clearCredentialStateAsync(
+            request,
             canceller,
             // Use a direct executor to avoid extra dispatch. Resuming the continuation will
             // handle getting to the right thread or pool via the ContinuationInterceptor.
@@ -177,7 +239,7 @@
         activity: Activity?,
         cancellationSignal: CancellationSignal?,
         executor: Executor,
-        callback: CredentialManagerCallback<GetCredentialResponse>,
+        callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
     ) {
         throw UnsupportedOperationException("Unimplemented")
     }
@@ -204,28 +266,34 @@
         activity: Activity?,
         cancellationSignal: CancellationSignal?,
         executor: Executor,
-        callback: CredentialManagerCallback<CreateCredentialResponse>,
+        callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>,
     ) {
         throw UnsupportedOperationException("Unimplemented")
     }
 
     /**
-     * Clears the current user credential session from all credential providers.
+     * Clears the current user credential state from all credential providers.
      *
-     * Usually invoked after your user signs out of your app so that they will not be
-     * automatically signed in the next time.
+     * You should invoked this api after your user signs out of your app to notify all credential
+     * providers that any stored credential session for the given app should be cleared.
      *
+     * A credential provider may have stored an active credential session and use it to limit
+     * sign-in options for future get-credential calls. For example, it may prioritize the active
+     * credential over any other available credential. When your user explicitly signs out of your
+     * app and in order to get the holistic sign-in options the next time, you should call this API
+     * to let the provider clear any stored credential session.
+     *
+     * @param request the request for clearing the app user's credential state
      * @param cancellationSignal an optional signal that allows for cancelling this call
      * @param executor the callback will take place on this executor
      * @param callback the callback invoked when the request succeeds or fails
      * @throws UnsupportedOperationException Since the api is unimplemented
-     *
-     * @hide
      */
-    fun clearCredentialSessionAsync(
+    fun clearCredentialStateAsync(
+        request: ClearCredentialStateRequest,
         cancellationSignal: CancellationSignal?,
         executor: Executor,
-        callback: CredentialManagerCallback<Void>,
+        callback: CredentialManagerCallback<Void, ClearCredentialException>,
     ) {
         throw UnsupportedOperationException("Unimplemented")
     }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialManagerCallback.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialManagerCallback.kt
index 76319ea..84bda52 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialManagerCallback.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialManagerCallback.kt
@@ -21,12 +21,13 @@
  * in a failure.
  *
  * This interface may be used in cases where an asynchronous Credential Manager API may complete
- * either with a value or with a [CredentialManagerException] that indicates an error.
+ * either with a value, or an exception.
  *
  * @param R the type of the result that's being sent
+ * @param E the type of the exception being returned
  */
-interface CredentialManagerCallback<R> {
+interface CredentialManagerCallback<R : Any, E : Any> {
     fun onResult(result: R)
 
-    fun onError(e: CredentialManagerException) {}
+    fun onError(e: E)
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialProvider.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialProvider.kt
index 0be5f0c..cb0c3ed 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialProvider.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialProvider.kt
@@ -18,6 +18,8 @@
 
 import android.app.Activity
 import android.os.CancellationSignal
+import androidx.credentials.exceptions.CreateCredentialException
+import androidx.credentials.exceptions.GetCredentialException
 import java.util.concurrent.Executor
 
 /**
@@ -58,7 +60,7 @@
         activity: Activity?, // TODO("Update on optionality")
         cancellationSignal: CancellationSignal?,
         executor: Executor,
-        callback: CredentialManagerCallback<GetCredentialResponse>,
+        callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
     )
 
     /**
@@ -75,7 +77,7 @@
         activity: Activity?,
         cancellationSignal: CancellationSignal?,
         executor: Executor,
-        callback: CredentialManagerCallback<CreateCredentialResponse>,
+        callback: CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>,
     )
 
     /** Determines whether the provider is available on this device, or not. */
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetCredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/GetCredentialOption.kt
index c75a157..6262f3d 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetCredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetCredentialOption.kt
@@ -17,20 +17,22 @@
 package androidx.credentials
 
 import android.os.Bundle
+import androidx.annotation.RestrictTo
 
 /**
  * Base class for getting a specific type of credentials.
  *
  * [GetCredentialRequest] will be composed of a list of [GetCredentialOption] subclasses to indicate
  * the specific credential types and configurations that your app accepts.
- *
- * @property type the credential type determined by the credential-type-specific subclass
- * @property data the request data in the [Bundle] format
- * @property requireSystemProvider true if must only be fulfilled by a system provider and false
- *                              otherwise
  */
 open class GetCredentialOption(
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     val type: String,
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     val data: Bundle,
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     val requireSystemProvider: Boolean,
 )
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt
index 165198a..3db5108 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt
@@ -25,10 +25,13 @@
  *
  * @property getCredentialOptions the list of [GetCredentialOption] from which the user can choose
  * one to authenticate to the app
+ * @property isAutoSelectAllowed defines if a credential entry will be automatically chosen if it is
+ * the only one, false by default
  * @throws IllegalArgumentException If [getCredentialOptions] is empty
  */
-class GetCredentialRequest constructor(
+class GetCredentialRequest @JvmOverloads constructor(
     val getCredentialOptions: List<GetCredentialOption>,
+    val isAutoSelectAllowed: Boolean = false,
 ) {
 
     init {
@@ -38,6 +41,7 @@
     /** A builder for [GetCredentialRequest]. */
     class Builder {
         private var getCredentialOptions: MutableList<GetCredentialOption> = mutableListOf()
+        private var autoSelectAllowed: Boolean = false
 
         /** Adds a specific type of [GetCredentialOption]. */
         fun addGetCredentialOption(getCredentialOption: GetCredentialOption): Builder {
@@ -52,12 +56,22 @@
         }
 
         /**
+         * Sets [autoSelectAllowed], which by default, is false.
+         */
+        @Suppress("MissingGetterMatchingBuilder")
+        fun setAutoSelectAllowed(autoSelectAllowed: Boolean): Builder {
+            this.autoSelectAllowed = autoSelectAllowed
+            return this
+        }
+
+        /**
          * Builds a [GetCredentialRequest].
          *
          * @throws IllegalArgumentException If [getCredentialOptions] is empty
          */
         fun build(): GetCredentialRequest {
-            return GetCredentialRequest(getCredentialOptions.toList())
+            return GetCredentialRequest(getCredentialOptions.toList(),
+                autoSelectAllowed)
         }
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialBaseOption.kt b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialBaseOption.kt
deleted file mode 100644
index 23cd3a3..0000000
--- a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialBaseOption.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.credentials
-
-import android.os.Bundle
-import androidx.annotation.VisibleForTesting
-
-/**
- * Base request class for getting a registered public key credential.
- *
- * @property requestJson the request in JSON format
- * @throws NullPointerException If [requestJson] is null - auto handled by the
- * Kotlin runtime
- * @throws IllegalArgumentException If [requestJson] is empty
- *
- * @hide
- */
-abstract class GetPublicKeyCredentialBaseOption constructor(
-    val requestJson: String,
-    type: String,
-    data: Bundle,
-    requireSystemProvider: Boolean,
-) : GetCredentialOption(type, data, requireSystemProvider) {
-
-    init {
-        require(requestJson.isNotEmpty()) { "request json must not be empty" }
-    }
-
-    /** @hide */
-    companion object {
-        @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
-        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
-    }
-}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt
index 7f449d4..ca12b5f 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt
@@ -22,29 +22,33 @@
 /**
  * A request to get passkeys from the user's public key credential provider.
  *
- * @property requestJson the request in JSON format
+ * @property requestJson the privileged request in JSON format in the standard webauthn web json
+ * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
  * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default
- * @throws NullPointerException If [requestJson] or [allowHybrid] is null. It is handled by the
- * Kotlin runtime
+ * true by default, with hybrid credentials defined
+ * [here](https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid)
+ * @throws NullPointerException If [requestJson] is null
  * @throws IllegalArgumentException If [requestJson] is empty
- *
- * @hide
  */
 class GetPublicKeyCredentialOption @JvmOverloads constructor(
-    requestJson: String,
+    val requestJson: String,
     @get:JvmName("allowHybrid")
     val allowHybrid: Boolean = true,
-) : GetPublicKeyCredentialBaseOption(
-    requestJson,
+) : GetCredentialOption(
     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
     toBundle(requestJson, allowHybrid),
     false
 ) {
+    init {
+        require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
+    }
+
     /** @hide */
     companion object {
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
         const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
 
         @JvmStatic
         internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle {
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOptionPrivileged.kt b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOptionPrivileged.kt
index 2228e7b..f3692cd 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOptionPrivileged.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOptionPrivileged.kt
@@ -22,39 +22,45 @@
 /**
  * A privileged request to get passkeys from the user's public key credential provider. The caller
  * can modify the RP. Only callers with privileged permission (e.g. user's public browser or caBLE)
- * can use this.
+ * can use this. These permissions will be introduced in an upcoming release.
+ * TODO("Add specific permission info/annotation")
  *
- * @property requestJson the privileged request in JSON format
+ * @property requestJson the privileged request in JSON format in the standard webauthn web json
+ * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
  * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default
- * @property rp the expected true RP ID which will override the one in the [requestJson]
- * @property clientDataHash a hash that is used to verify the [rp] Identity
- * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash]
- * is null. This is handled by the Kotlin runtime
- * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty
- *
- * @hide
+ * true by default, with hybrid credentials defined
+ * [here](https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid)
+ * @property relyingParty the expected true RP ID which will override the one in the [requestJson],
+ * where relyingParty is defined [here](https://w3c.github.io/webauthn/#rp-id) in more detail
+ * @property clientDataHash a hash that is used to verify the [relyingParty] Identity
+ * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash]
+ * is null
+ * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is empty
  */
 class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor(
-    requestJson: String,
-    val rp: String,
+    val requestJson: String,
+    val relyingParty: String,
     val clientDataHash: String,
     @get:JvmName("allowHybrid")
     val allowHybrid: Boolean = true
-) : GetPublicKeyCredentialBaseOption(
-    requestJson,
+) : GetCredentialOption(
     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-    toBundle(requestJson, rp, clientDataHash, allowHybrid),
+    toBundle(requestJson, relyingParty, clientDataHash, allowHybrid),
     false,
 ) {
 
     init {
-        require(rp.isNotEmpty()) { "rp must not be empty" }
+        require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
+        require(relyingParty.isNotEmpty()) { "rp must not be empty" }
         require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" }
     }
 
     /** A builder for [GetPublicKeyCredentialOptionPrivileged]. */
-    class Builder(var requestJson: String, var rp: String, var clientDataHash: String) {
+    class Builder(
+        private var requestJson: String,
+        private var relyingParty: String,
+        private var clientDataHash: String
+        ) {
 
         private var allowHybrid: Boolean = true
 
@@ -69,6 +75,7 @@
         /**
          * Sets whether hybrid credentials are allowed to fulfill this request, true by default.
          */
+        @Suppress("MissingGetterMatchingBuilder")
         fun setAllowHybrid(allowHybrid: Boolean): Builder {
             this.allowHybrid = allowHybrid
             return this
@@ -77,13 +84,13 @@
         /**
          * Sets the expected true RP ID which will override the one in the [requestJson].
          */
-        fun setRp(rp: String): Builder {
-            this.rp = rp
+        fun setRelyingParty(relyingParty: String): Builder {
+            this.relyingParty = relyingParty
             return this
         }
 
         /**
-         * Sets a hash that is used to verify the [rp] Identity.
+         * Sets a hash that is used to verify the [relyingParty] Identity.
          */
         fun setClientDataHash(clientDataHash: String): Builder {
             this.clientDataHash = clientDataHash
@@ -93,30 +100,32 @@
         /** Builds a [GetPublicKeyCredentialOptionPrivileged]. */
         fun build(): GetPublicKeyCredentialOptionPrivileged {
             return GetPublicKeyCredentialOptionPrivileged(this.requestJson,
-                this.rp, this.clientDataHash, this.allowHybrid)
+                this.relyingParty, this.clientDataHash, this.allowHybrid)
         }
     }
 
     /** @hide */
     companion object {
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP"
+        const val BUNDLE_KEY_RELYING_PARTY = "androidx.credentials.BUNDLE_KEY_RELYING_PARTY"
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
         const val BUNDLE_KEY_CLIENT_DATA_HASH =
             "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
         const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
 
         @JvmStatic
         internal fun toBundle(
             requestJson: String,
-            rp: String,
+            relyingParty: String,
             clientDataHash: String,
             allowHybrid: Boolean
         ): Bundle {
             val bundle = Bundle()
             bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
-            bundle.putString(BUNDLE_KEY_RP, rp)
+            bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty)
             bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
             bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
             return bundle
diff --git a/credentials/credentials/src/main/java/androidx/credentials/PublicKeyCredential.kt b/credentials/credentials/src/main/java/androidx/credentials/PublicKeyCredential.kt
index 1a87b3c..ac9010a 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/PublicKeyCredential.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/PublicKeyCredential.kt
@@ -25,11 +25,8 @@
  * @property authenticationResponseJson the public key credential authentication response in
  * JSON format that follows the standard webauthn json format shown at
  * [this w3c link](https://w3c.github.io/webauthn/#dictdef-authenticationresponsejson)
- * @throws NullPointerException If [authenticationResponseJson] is null. This is handled by the
- * kotlin runtime
+ * @throws NullPointerException If [authenticationResponseJson] is null
  * @throws IllegalArgumentException If [authenticationResponseJson] is empty
- *
- * @hide
  */
 class PublicKeyCredential constructor(
     val authenticationResponseJson: String
@@ -42,8 +39,11 @@
         require(authenticationResponseJson.isNotEmpty()) {
             "authentication response JSON must not be empty" }
     }
+
+    /** @hide */
     companion object {
         /** The type value for public key credential related operations. */
+        /** @hide */
         const val TYPE_PUBLIC_KEY_CREDENTIAL: String =
             "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
 
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/ClearCredentialException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/ClearCredentialException.kt
new file mode 100644
index 0000000..18c7dbe
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/ClearCredentialException.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.RestrictTo
+import androidx.credentials.CredentialManager
+
+/**
+ * Represents an error encountered during a clear flow with Credential Manager. See
+ * [CredentialManager] for more details on how Credentials work for Credential Manager flows.
+ *
+ * @see CredentialManager
+ * @see ClearCredentialInterruptedException
+ * @see ClearCredentialUnknownException
+ *
+ * @property errorMessage a human-readable string that describes the error
+ * @throws NullPointerException if [type] is null
+ * @throws IllegalArgumentException if [type] is empty
+ */
+open class ClearCredentialException @JvmOverloads constructor(
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    val type: String,
+    val errorMessage: CharSequence? = null
+) : Exception(errorMessage?.toString()) {
+    init {
+        require(type.isNotEmpty()) { "type must not be empty" }
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/ClearCredentialInterruptedException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/ClearCredentialInterruptedException.kt
new file mode 100644
index 0000000..a9793d2
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/ClearCredentialInterruptedException.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.VisibleForTesting
+
+/**
+ * During the clear credential flow, this is called when some interruption occurs that may warrant
+ * retrying or at least does not indicate a purposeful desire to close or tap away from credential
+ * manager.
+ *
+ * @see ClearCredentialException
+ */
+class ClearCredentialInterruptedException @JvmOverloads constructor(
+    errorMessage: CharSequence? = null
+) : ClearCredentialException(TYPE_CLEAR_CREDENTIAL_INTERRUPTED_EXCEPTION, errorMessage) {
+    /** @hide */
+    companion object {
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val TYPE_CLEAR_CREDENTIAL_INTERRUPTED_EXCEPTION: String =
+            "androidx.credentials.TYPE_CLEAR_CREDENTIAL_INTERRUPTED_EXCEPTION"
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/ClearCredentialUnknownException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/ClearCredentialUnknownException.kt
new file mode 100644
index 0000000..56308eb
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/ClearCredentialUnknownException.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.VisibleForTesting
+
+/**
+ * This clear credential operation failed with no more detailed information.
+ *
+ * @see ClearCredentialException
+ */
+class ClearCredentialUnknownException @JvmOverloads constructor(
+    errorMessage: CharSequence? = null
+) : ClearCredentialException(TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION, errorMessage) {
+
+    /** @hide */
+    companion object {
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION: String =
+            "androidx.credentials.TYPE_CLEAR_CREDENTIAL_UNKNOWN_EXCEPTION"
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialCanceledException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialCanceledException.kt
new file mode 100644
index 0000000..2c205c4
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialCanceledException.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.VisibleForTesting
+
+/**
+ * During the create credential flow, this is called when a user intentionally cancels an operation.
+ * When this happens, the application should handle logic accordingly, typically under indication
+ * the user does not want to see Credential Manager anymore.
+ *
+ * @see CreateCredentialException
+ */
+class CreateCredentialCanceledException @JvmOverloads constructor(
+    errorMessage: CharSequence? = null
+) : CreateCredentialException(TYPE_CREATE_CREDENTIAL_CANCELED_EXCEPTION, errorMessage) {
+
+    /** @hide */
+    companion object {
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val TYPE_CREATE_CREDENTIAL_CANCELED_EXCEPTION: String =
+            "androidx.credentials.TYPE_CREATE_CREDENTIAL_CANCELED_EXCEPTION"
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialException.kt
new file mode 100644
index 0000000..ec8c14b
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialException.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.RestrictTo
+import androidx.credentials.CredentialManager
+
+/**
+ * Represents an error encountered during a create flow with Credential Manager. See
+ * [CredentialManager] for more details on how Credentials work for Credential Manager flows.
+ *
+ * @see CredentialManager
+ * @see CreateCredentialInterruptedException
+ * @see CreateCredentialCanceledException
+ * @see CreateCredentialUnknownException
+ *
+ * @property errorMessage a human-readable string that describes the error
+ * @throws NullPointerException if [type] is null
+ * @throws IllegalArgumentException if [type] is empty
+*/
+open class CreateCredentialException @JvmOverloads constructor(
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    val type: String,
+    val errorMessage: CharSequence? = null
+) : Exception(errorMessage?.toString()) {
+    init {
+        require(type.isNotEmpty()) { "type must not be empty" }
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialInterruptedException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialInterruptedException.kt
new file mode 100644
index 0000000..9dbaacb
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialInterruptedException.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.VisibleForTesting
+
+/**
+ * During the create credential flow, this is called when some interruption occurs that may warrant
+ * retrying or at least does not indicate a purposeful desire to close or tap away from credential
+ * manager.
+ *
+ * @see CreateCredentialException
+ */
+class CreateCredentialInterruptedException @JvmOverloads constructor(
+    errorMessage: CharSequence? = null
+) : CreateCredentialException(TYPE_CREATE_CREDENTIAL_INTERRUPTED_EXCEPTION, errorMessage) {
+
+    /** @hide */
+    companion object {
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val TYPE_CREATE_CREDENTIAL_INTERRUPTED_EXCEPTION: String =
+            "androidx.credentials.TYPE_CREATE_CREDENTIAL_INTERRUPTED_EXCEPTION"
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialUnknownException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialUnknownException.kt
new file mode 100644
index 0000000..1fd9dc2
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/CreateCredentialUnknownException.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.VisibleForTesting
+
+/**
+ * This create credential operation failed with no more detailed information.
+ *
+ * @see CreateCredentialException
+ */
+class CreateCredentialUnknownException @JvmOverloads constructor(
+    errorMessage: CharSequence? = null
+) : CreateCredentialException(TYPE_CREATE_CREDENTIAL_UNKNOWN_EXCEPTION, errorMessage) {
+
+    /** @hide */
+    companion object {
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val TYPE_CREATE_CREDENTIAL_UNKNOWN_EXCEPTION: String =
+            "androidx.credentials.TYPE_CREATE_CREDENTIAL_UNKNOWN_EXCEPTION"
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialCanceledException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialCanceledException.kt
new file mode 100644
index 0000000..5ed263f
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialCanceledException.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.VisibleForTesting
+
+/**
+ * During the get credential flow, this is called when a user intentionally cancels an operation.
+ * When this happens, the application should handle logic accordingly, typically under indication
+ * the user does not want to see Credential Manager anymore.
+ *
+ * @see GetCredentialException
+ */
+class GetCredentialCanceledException @JvmOverloads constructor(
+    errorMessage: CharSequence? = null
+) : GetCredentialException(TYPE_GET_CREDENTIAL_CANCELED_EXCEPTION, errorMessage) {
+
+    /** @hide */
+    companion object {
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val TYPE_GET_CREDENTIAL_CANCELED_EXCEPTION: String =
+            "androidx.credentials.TYPE_GET_CREDENTIAL_CANCELED_EXCEPTION"
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialException.kt
new file mode 100644
index 0000000..372584c
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialException.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.RestrictTo
+import androidx.credentials.CredentialManager
+
+/**
+ * Represents an error encountered during a get flow with Credential Manager. See
+ * [CredentialManager] for more details on how Credentials work for Credential Manager flows.
+ *
+ * @see CredentialManager
+ * @see GetCredentialUnknownException
+ * @see GetCredentialCanceledException
+ * @see GetCredentialInterruptedException
+ *
+ * @property errorMessage a human-readable string that describes the error
+ * @throws NullPointerException if [type] is null
+ * @throws IllegalArgumentException if [type] is empty
+ */
+open class GetCredentialException @JvmOverloads constructor(
+    /** @hide */
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    val type: String,
+    val errorMessage: CharSequence? = null
+) : Exception(errorMessage?.toString()) {
+    init {
+        require(type.isNotEmpty()) { "type must not be empty" }
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialInterruptedException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialInterruptedException.kt
new file mode 100644
index 0000000..f352c65
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialInterruptedException.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.VisibleForTesting
+
+/**
+ * During the get credential flow, this is called when some interruption occurs that may warrant
+ * retrying or at least does not indicate a purposeful desire to close or tap away from credential
+ * manager.
+ *
+ * @see GetCredentialException
+ */
+class GetCredentialInterruptedException @JvmOverloads constructor(
+    errorMessage: CharSequence? = null
+) : GetCredentialException(TYPE_GET_CREDENTIAL_INTERRUPTED_EXCEPTION, errorMessage) {
+
+    /** @hide */
+    companion object {
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val TYPE_GET_CREDENTIAL_INTERRUPTED_EXCEPTION: String =
+            "androidx.credentials.TYPE_GET_CREDENTIAL_INTERRUPTED_EXCEPTION"
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialUnknownException.kt b/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialUnknownException.kt
new file mode 100644
index 0000000..dbef033
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/exceptions/GetCredentialUnknownException.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.exceptions
+
+import androidx.annotation.VisibleForTesting
+
+/**
+ * This get credential operation failed with no more detailed information.
+ *
+ * @see GetCredentialException
+ */
+class GetCredentialUnknownException @JvmOverloads constructor(
+    errorMessage: CharSequence? = null
+) : GetCredentialException(TYPE_GET_CREDENTIAL_UNKNOWN_EXCEPTION, errorMessage) {
+
+    /** @hide */
+    companion object {
+        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+        const val TYPE_GET_CREDENTIAL_UNKNOWN_EXCEPTION: String =
+            "androidx.credentials.TYPE_GET_CREDENTIAL_UNKNOWN_EXCEPTION"
+    }
+}
\ No newline at end of file
diff --git a/development/auto-version-updater/test_update_versions_for_release.py b/development/auto-version-updater/test_update_versions_for_release.py
index fc27d84..e6e9a9f 100755
--- a/development/auto-version-updater/test_update_versions_for_release.py
+++ b/development/auto-version-updater/test_update_versions_for_release.py
@@ -20,6 +20,11 @@
 from update_versions_for_release import *
 from shutil import rmtree
 
+# Import functions from the parent directory
+sys.path.append("..")
+from update_tracing_perfetto import single
+from update_tracing_perfetto import sed
+
 class TestVersionUpdates(unittest.TestCase):
 
     def test_increment_version(self):
diff --git a/development/auto-version-updater/update_tracing_perfetto.sh b/development/auto-version-updater/update_tracing_perfetto.sh
deleted file mode 100755
index 349dd4b..0000000
--- a/development/auto-version-updater/update_tracing_perfetto.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env bash
-
-#
-# Usage:
-# ./update_perfetto.sh ANDROIDX_CHECKOUT CURRENT_VERSION NEW_VERSION
-#
-# Example:
-# ./update_perfetto.sh /Volumes/android/androidx-main/frameworks/support 1.0.0-alpha04 1.0.0-alpha05
-#
-
-set -euo pipefail
-
-ANDROIDX_CHECKOUT="$(cd "$1"; pwd -P .)" # gets absolute path of root dir
-CURRENT_VERSION="$2"
-NEW_VERSION="$3"
-
-/usr/bin/env python3 <<EOF
-from update_versions_for_release import update_tracing_perfetto
-update_tracing_perfetto('$CURRENT_VERSION', '$NEW_VERSION', "$ANDROIDX_CHECKOUT")
-EOF
diff --git a/development/auto-version-updater/update_versions_for_release.py b/development/auto-version-updater/update_versions_for_release.py
index 98c9091..0cd1cd8 100755
--- a/development/auto-version-updater/update_versions_for_release.py
+++ b/development/auto-version-updater/update_versions_for_release.py
@@ -21,12 +21,14 @@
 import glob
 import pathlib
 import re
+import shutil
 import subprocess
 import toml
 
 # Import the JetpadClient from the parent directory
 sys.path.append("..")
 from JetpadClient import *
+from update_tracing_perfetto import update_tracing_perfetto
 
 # cd into directory of script
 os.chdir(os.path.dirname(os.path.abspath(__file__)))
@@ -79,42 +81,6 @@
         print("Please respond with y/n")
 
 
-def sed(pattern, replacement, file):
-    """ Performs an in-place string replacement of pattern in a target file
-
-    Args:
-        pattern: pattern to replace
-        replacement: replacement for the pattern matches
-        file: target file
-
-    Returns:
-        Nothing
-    """
-
-    with open(file) as f:
-        file_contents = f.read()
-    new_file_contents = re.sub(pattern, replacement, file_contents)
-    with open(file, "w") as f:
-        f.write(new_file_contents)
-
-
-def single(list):
-    """ Returns the only item from a list of just one item
-
-    Raises a ValueError if the list does not contain exactly one element
-
-    Args:
-        list: a list of one item
-
-    Returns:
-        The only item from a single-item-list
-    """
-
-    if len(list) != 1:
-        raise ValueError('Expected a list of size 1. Found: %s' % list)
-    return list[0]
-
-
 def run_update_api():
     """Runs updateApi ignoreApiChanges from the frameworks/support root.
     """
@@ -495,81 +461,6 @@
     return
 
 
-def update_tracing_perfetto(old_version, new_version=None, core_path=FRAMEWORKS_SUPPORT_FP):
-    """Updates tracing-perfetto version and artifacts (including building new binaries)
-
-    Args:
-        old_version: old version of the existing library
-        new_version: new version of the library; defaults to incrementing the old_version
-        core_path: path to frameworks/support directory
-    Returns:
-        Nothing
-    """
-
-    print("Updating tracing-perfetto, this can take a while...")
-
-    # update version in code
-    if not new_version:
-        new_version = increment_version(old_version)
-
-    sed('tracingPerfettoVersion = "%s"' % old_version,
-        'tracingPerfettoVersion = "%s"' % new_version,
-        os.path.join(core_path, 'benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/'
-                                'macro/perfetto/PerfettoSdkHandshakeTest.kt'))
-    sed('TRACING_PERFETTO = "%s"' % old_version,
-        'TRACING_PERFETTO = "%s"' % new_version,
-        os.path.join(core_path, 'libraryversions.toml'))
-    sed('#define VERSION "%s"' % old_version,
-        '#define VERSION "%s"' % new_version,
-        os.path.join(core_path, 'tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc'))
-    sed('const val libraryVersion = "%s"' % old_version,
-        'const val libraryVersion = "%s"' % new_version,
-        os.path.join(core_path, 'tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/'
-                                'perfetto/jni/test/PerfettoNativeTest.kt'))
-    sed('const val version = "%s"' % old_version,
-        'const val version = "%s"' % new_version,
-        os.path.join(core_path, 'tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/'
-                                'jni/PerfettoNative.kt'))
-
-    # build new binaries
-    subprocess.check_call(["./gradlew",
-                           ":tracing:tracing-perfetto-binary:createProjectZip",
-                           "-DTRACING_PERFETTO_REUSE_PREBUILTS_AAR=false"],
-                          cwd=core_path)
-
-    # copy binaries to prebuilts
-    project_zip_dir = os.path.join(core_path, '../../out/dist/per-project-zips')
-    project_zip_file = os.path.join(
-        project_zip_dir,
-        single(glob.glob('%s/*tracing*perfetto*binary*%s*.zip' % (project_zip_dir, new_version))))
-    dst_dir = pathlib.Path(os.path.join(
-        core_path,
-        "../../prebuilts/androidx/internal/androidx/tracing/tracing-perfetto-binary",
-        new_version))
-    if dst_dir.exists():
-        shutil.rmtree(dst_dir)
-    dst_dir.mkdir()
-    subprocess.check_call(
-        ["unzip", "-xjqq", project_zip_file, '**/%s/**' % new_version, "-d", dst_dir])
-
-    # update SHA
-    for arch in ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64']:
-        checksum = subprocess.check_output(
-            'unzip -cxqq "*tracing*binary*%s*.aar" "**/%s/libtracing_perfetto.so" | shasum -a256 |'
-            ' awk \'{print $1}\' | tr -d "\n"' % (new_version, arch),
-            cwd=dst_dir,
-            shell=True
-        ).decode()
-        if not re.fullmatch('^[0-9a-z]{64}$', checksum):
-            raise ValueError('Expecting a sha256 sum. Got: %s' % checksum)
-        sed(
-            '"%s" to "[0-9a-z]{64}"' % arch,
-            '"%s" to "%s"' % (arch, checksum),
-            os.path.join(core_path, 'tracing/tracing-perfetto/src/main/java/androidx/tracing/'
-                                    'perfetto/jni/PerfettoNative.kt'))
-
-    print("Updated tracing-perfetto.")
-
 def commit_updates(release_date):
     for dir in [FRAMEWORKS_SUPPORT_FP, PREBUILTS_ANDROIDX_INTERNAL_FP]:
         subprocess.check_call(["git", "add", "."], cwd=dir, stderr=subprocess.STDOUT)
@@ -611,7 +502,9 @@
                 if tracing_perfetto_updated:
                     updated = True
                 else:
-                    update_tracing_perfetto(artifact["version"])
+                    current_version = artifact["version"]
+                    target_version = increment_version(current_version)
+                    update_tracing_perfetto(current_version, target_version, FRAMEWORKS_SUPPORT_FP)
                     tracing_perfetto_updated = True
 
             if not updated:
diff --git a/development/update_tracing_perfetto.py b/development/update_tracing_perfetto.py
new file mode 100755
index 0000000..333ca7b
--- /dev/null
+++ b/development/update_tracing_perfetto.py
@@ -0,0 +1,190 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import glob
+import argparse
+import pathlib
+import shutil
+import subprocess
+import re
+
+
+def sed(pattern, replacement, file):
+    """ Performs an in-place string replacement of pattern in a target file
+
+    Args:
+        pattern: pattern to replace
+        replacement: replacement for the pattern matches
+        file: target file
+
+    Returns:
+        Nothing
+    """
+
+    with open(file) as reader:
+        file_contents = reader.read()
+    new_file_contents = re.sub(pattern, replacement, file_contents)
+    with open(file, "w") as writer:
+        writer.write(new_file_contents)
+
+
+def single(items):
+    """ Returns the only item from a list of just one item
+
+    Raises a ValueError if the list does not contain exactly one element
+
+    Args:
+        items: a list of one item
+
+    Returns:
+        The only item from a single-item-list
+    """
+
+    if len(items) != 1:
+        raise ValueError('Expected a list of size 1. Found: %s' % items)
+    return items[0]
+
+
+def update_tracing_perfetto(old_version, new_version, core_path, force_unstripped_binaries=False):
+    """Updates tracing-perfetto version and artifacts (including building new binaries)
+
+    Args:
+        old_version: old version of the existing library
+        new_version: new version of the library; defaults to incrementing the old_version
+        core_path: path to frameworks/support directory
+        force_unstripped_binaries: flag allowing to force unstripped variant of binaries
+    Returns:
+        Nothing
+    """
+
+    print("Updating tracing-perfetto, this can take a while...")
+
+    # update version in code
+    sed('tracingPerfettoVersion = "%s"' % old_version,
+        'tracingPerfettoVersion = "%s"' % new_version,
+        os.path.join(core_path, 'benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/'
+                                'macro/perfetto/PerfettoSdkHandshakeTest.kt'))
+    sed('TRACING_PERFETTO = "%s"' % old_version,
+        'TRACING_PERFETTO = "%s"' % new_version,
+        os.path.join(core_path, 'libraryversions.toml'))
+    sed('#define VERSION "%s"' % old_version,
+        '#define VERSION "%s"' % new_version,
+        os.path.join(core_path, 'tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc'))
+    sed('const val libraryVersion = "%s"' % old_version,
+        'const val libraryVersion = "%s"' % new_version,
+        os.path.join(core_path, 'tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/'
+                                'perfetto/jni/test/PerfettoNativeTest.kt'))
+    sed('const val version = "%s"' % old_version,
+        'const val version = "%s"' % new_version,
+        os.path.join(core_path, 'tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/'
+                                'jni/PerfettoNative.kt'))
+
+    # build new binaries
+    subprocess.check_call(["./gradlew",
+                           ":tracing:tracing-perfetto-binary:createProjectZip",
+                           "-DTRACING_PERFETTO_REUSE_PREBUILTS_AAR=false"],
+                          cwd=core_path)
+
+    # copy binaries to prebuilts
+    project_zip_dir = os.path.join(core_path, '../../out/dist/per-project-zips')
+    project_zip_file = os.path.join(
+        project_zip_dir,
+        single(glob.glob('%s/*tracing*perfetto*binary*%s*.zip' % (project_zip_dir, new_version))))
+    dst_dir = pathlib.Path(os.path.join(
+        core_path,
+        "../../prebuilts/androidx/internal/androidx/tracing/tracing-perfetto-binary",
+        new_version))
+    if dst_dir.exists():
+        shutil.rmtree(dst_dir)
+    dst_dir.mkdir()
+    subprocess.check_call(
+        ["unzip", "-xjqq", project_zip_file, '**/%s/**' % new_version, "-d", dst_dir])
+
+    # force unstripped binaries if the flag is enabled
+    if force_unstripped_binaries:
+        # locate unstripped binaries
+        out_dir = pathlib.Path(core_path, "../../out")
+        arm64_lib_file = out_dir.joinpath(single(subprocess.check_output(
+            'find . -type f -name "libtracing_perfetto.so"'
+            ' -and -path "*RelWithDebInfo/*/obj/arm64*"'
+            ' -exec stat -c "%Y %n" {} \\; |'
+            ' sort | tail -1 | cut -d " " -f2-',
+            cwd=out_dir,
+            shell=True).splitlines()).decode())
+        base_dir = arm64_lib_file.parent.parent.parent
+        obj_dir = base_dir.joinpath('obj')
+        if not obj_dir.exists():
+            raise RuntimeError('Expected path %s to exist' % repr(obj_dir))
+        jni_dir = base_dir.joinpath('jni')
+
+        # prepare a jni folder to inject into the destination aar
+        if jni_dir.exists():
+            shutil.rmtree(jni_dir)
+        shutil.copytree(obj_dir, jni_dir)
+
+        # inject the jni folder into the aar
+        dst_aar = os.path.join(dst_dir, 'tracing-perfetto-binary-%s.aar' % new_version)
+        subprocess.check_call(['zip', '-r', dst_aar, 'jni'], cwd=base_dir)
+
+        # clean up
+        if jni_dir.exists():
+            shutil.rmtree(jni_dir)
+
+    # update SHA
+    for arch in ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64']:
+        checksum = subprocess.check_output(
+            'unzip -cxqq "*tracing*binary*%s*.aar" "**/%s/libtracing_perfetto.so" | shasum -a256 |'
+            ' awk \'{print $1}\' | tr -d "\n"' % (new_version, arch),
+            cwd=dst_dir,
+            shell=True
+        ).decode()
+        if not re.fullmatch('^[0-9a-z]{64}$', checksum):
+            raise ValueError('Expecting a sha256 sum. Got: %s' % checksum)
+        sed(
+            '"%s" to "[0-9a-z]{64}"' % arch,
+            '"%s" to "%s"' % (arch, checksum),
+            os.path.join(core_path, 'tracing/tracing-perfetto/src/main/java/androidx/tracing/'
+                                    'perfetto/jni/PerfettoNative.kt'))
+
+    print("Updated tracing-perfetto.")
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(
+        description='Updates tracing-perfetto in the source code, which involves:'
+                    ' 1) updating hardcoded version references in code'
+                    ' 2) building binaries and updating them in the prebuilts folder'
+                    ' 3) updating SHA checksums hardcoded in code.')
+    parser.add_argument('-f', '--frameworks-support-dir',
+                        required=True,
+                        help='Path to frameworks/support directory')
+    parser.add_argument('-c', '--current-version',
+                        required=True,
+                        help='Current version, e.g. 1.0.0-alpha07')
+    parser.add_argument('-t', '--target-version',
+                        required=True,
+                        help='Target version, e.g. 1.0.0-alpha08')
+    parser.add_argument('-k', '--keep-binary-debug-symbols',
+                        required=False,
+                        default=False,
+                        action='store_true',
+                        help='Keeps debug symbols in the built binaries. Useful when profiling '
+                             'performance of the library. ')
+    args = parser.parse_args()
+    core_path_abs = pathlib.Path(args.frameworks_support_dir).resolve()
+    update_tracing_perfetto(args.current_version, args.target_version, core_path_abs,
+                            args.keep_binary_debug_symbols)
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-af/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-af/strings.xml
index ba26a9a..21f04a3 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-af/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-af/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOLE"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"VLAE"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Geen emosiekone beskikbaar nie"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Jy het nog geen emosiekone gebruik nie"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-am/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-am/strings.xml
index 8bced7b..b3cbd77 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-am/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-am/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ነገሮች"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ምልክቶች"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ባንዲራዎች"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"ምንም ስሜት ገላጭ ምስሎች አይገኙም"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
index dbee125..170353c 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"الأشياء"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"الرموز"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"الأعلام"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"لا تتوفر أي رموز تعبيرية."</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-as/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-as/strings.xml
index 27f9a48..f1f6fbef 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-as/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-as/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"বস্তু"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"চিহ্ন"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"পতাকা"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"কোনো ইম’জি উপলব্ধ নহয়"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-az/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-az/strings.xml
index 494a404..78ed86d 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-az/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-az/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBYEKTLƏR"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SİMVOLLAR"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BAYRAQLAR"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Əlçatan emoji yoxdur"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-b+sr+Latn/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-b+sr+Latn/strings.xml
index 082c070..ec5ddd5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-b+sr+Latn/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-b+sr+Latn/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJEKTI"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOLI"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ZASTAVE"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Emodžiji nisu dostupni"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Još niste koristili emodžije"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml
index b7f1775..eda0664 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-be/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"АБ\'ЕКТЫ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"СІМВАЛЫ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"СЦЯГІ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Няма даступных эмодзі"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-bg/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-bg/strings.xml
index 5bc64c0..d22d25b 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-bg/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-bg/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ПРЕДМЕТИ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"СИМВОЛИ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ЗНАМЕНА"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Няма налични емоджи"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-bn/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-bn/strings.xml
index ce02e6c..3fd2fae 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-bn/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-bn/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"অবজেক্ট"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"প্রতীক"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ফ্ল্যাগ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"কোনও ইমোজি উপলভ্য নেই"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml
index f939f3a..d189ac9 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml
@@ -26,5 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"PREDMETI"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOLI"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ZASTAVE"</string>
-    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nije dostupan nijedan emoji"</string>
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Emoji sličice nisu dostupne"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Još niste upotrijebili emojije"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ca/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ca/strings.xml
index 7b560c7..4644a8b 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ca/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ca/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJECTES"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SÍMBOLS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BANDERES"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No hi ha cap emoji disponible"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-cs/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-cs/strings.xml
index 297d62d..f3932fe 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-cs/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-cs/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJEKTY"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLY"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"VLAJKY"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nejsou k dispozici žádné smajlíky"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-da/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-da/strings.xml
index 4302216..ca542b0 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-da/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-da/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"TING"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLER"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAG"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Der er ingen tilgængelige emojis"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Du har ikke brugt nogen emojis endnu"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml
index 4765437..e836340 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJEKTE"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLE"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAGGEN"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Keine Emojis verfügbar"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-el/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-el/strings.xml
index 8b49435..64e701f 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-el/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-el/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ΑΝΤΙΚΕΙΜΕΝΑ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ΣΥΜΒΟΛΑ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ΣΗΜΑΙΕΣ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Δεν υπάρχουν διαθέσιμα emoji"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Δεν έχετε χρησιμοποιήσει κανένα emoji ακόμα"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-en-rAU/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-en-rAU/strings.xml
index 7b7b1f7..828f853 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-en-rAU/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-en-rAU/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAGS"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No emojis available"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"You haven\'t used any emoji yet"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-en-rCA/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-en-rCA/strings.xml
index 7b7b1f7..23ba7f6 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-en-rCA/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-en-rCA/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAGS"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No emojis available"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"You haven\'t used any emojis yet"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-en-rGB/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-en-rGB/strings.xml
index 7b7b1f7..828f853 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-en-rGB/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-en-rGB/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAGS"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No emojis available"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"You haven\'t used any emoji yet"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-en-rIN/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-en-rIN/strings.xml
index 7b7b1f7..828f853 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-en-rIN/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-en-rIN/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAGS"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No emojis available"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"You haven\'t used any emoji yet"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-en-rXC/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-en-rXC/strings.xml
index ef401e8..7cacbb0 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-en-rXC/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-en-rXC/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‏‏‎‏‏‎SYMBOLS‎‏‎‎‏‎"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‎FLAGS‎‏‎‎‏‎"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎‎‎‎‎‏‎‏‎‏‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎No emojis available‎‏‎‎‏‎"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎You haven\'t used any emojis yet‎‏‎‎‏‎"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-es-rUS/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-es-rUS/strings.xml
index 688cecb..790b4e6 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-es-rUS/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-es-rUS/strings.xml
@@ -27,4 +27,6 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SÍMBOLOS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BANDERAS"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No hay ningún emoji disponible"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
+    <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml
index 3047306..3f46a8d 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJETOS"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SÍMBOLOS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BANDERAS"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"No hay emojis disponibles"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Aún no has usado ningún emoji"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-et/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-et/strings.xml
index 6d9cfc9..2ea6dcd 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-et/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-et/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJEKTID"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SÜMBOLID"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"LIPUD"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Ühtegi emotikoni pole saadaval"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-eu/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-eu/strings.xml
index 86d8285..71f27ef 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-eu/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-eu/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJEKTUAK"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"IKURRAK"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BANDERAK"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Ez dago emotikonorik erabilgarri"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Ez duzu erabili emojirik oraingoz"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml
index cc8c523..8be716a 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"اشیاء"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"نمادها"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"پرچم‌ها"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"اموجی دردسترس نیست"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-fi/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-fi/strings.xml
index 9d90888..31f6de1 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-fi/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-fi/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ESINEET"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLIT"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"LIPUT"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Ei emojeita saatavilla"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-fr-rCA/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-fr-rCA/strings.xml
index 1d539cd..2704128 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-fr-rCA/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-fr-rCA/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJETS"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLES"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"DRAPEAUX"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Aucun émoji proposé"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Vous n\'avez encore utilisé aucun émoji"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-fr/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-fr/strings.xml
index 1bc01ec..35a8d76 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-fr/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-fr/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJETS"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLES"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"DRAPEAUX"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Aucun emoji disponible"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-gl/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-gl/strings.xml
index 9a13966..857e179 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-gl/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-gl/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBXECTOS"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SÍMBOLOS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BANDEIRAS"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Non hai ningún emoji dispoñible"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-gu/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-gu/strings.xml
index d0b15fe..565cd02 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-gu/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-gu/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ઑબ્જેક્ટ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"પ્રતીકો"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ઝંડા"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"કોઈ ઇમોજી ઉપલબ્ધ નથી"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-hi/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-hi/strings.xml
index e3a1b22..c61cc03 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-hi/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-hi/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ऑब्जेक्ट"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"सिंबल"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"झंडे"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"कोई इमोजी उपलब्ध नहीं है"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-hr/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-hr/strings.xml
index a2dcb08..54b7e79 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-hr/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-hr/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOLI"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ZASTAVE"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nije dostupan nijedan emoji"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Još niste upotrijebili emojije"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-hu/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-hu/strings.xml
index 57fbcd8d..0848ad2 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-hu/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-hu/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"TÁRGYAK"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SZIMBÓLUMOK"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ZÁSZLÓK"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nincsenek rendelkezésre álló emojik"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-hy/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-hy/strings.xml
index 7842e75..4ac80b5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-hy/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-hy/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ԱՌԱՐԿԱՆԵՐ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ՆՇԱՆՆԵՐ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ԴՐՈՇՆԵՐ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Հասանելի էմոջիներ չկան"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-in/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-in/strings.xml
index fcb4268..7b4f962 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-in/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-in/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJEK"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOL"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BENDERA"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Tidak ada emoji yang tersedia"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml
index 86c6d24..f743f26 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"HLUTIR"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"TÁKN"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FÁNAR"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Engin emoji-tákn í boði"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-it/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-it/strings.xml
index 91b2d1e..9f9b707 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-it/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-it/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OGGETTI"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOLI"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BANDIERE"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nessuna emoji disponibile"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-iw/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-iw/strings.xml
index 2114632..db00c09 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-iw/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-iw/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"אובייקטים"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"סמלים"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"דגלים"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"אין סמלי אמוג\'י זמינים"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ja/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ja/strings.xml
index d4bad63..cbccd1f 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ja/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ja/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"アイテム"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"記号"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"旗"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"使用できる絵文字がありません"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"まだ絵文字を使用していません"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ka/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ka/strings.xml
index fc2be1d..3e33c41 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ka/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ka/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ობიექტები"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"სიმბოლოები"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"დროშები"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Emoji-ები მიუწვდომელია"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Emoji-ებით ჯერ არ გისარგებლიათ"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml
index 9ea1326..22c8157 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"НЫСАНДАР"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ТАҢБАЛАР"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ЖАЛАУШАЛАР"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Эмоджи жоқ"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml
index 180d981..b79617d 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"វត្ថុ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"និមិត្តសញ្ញា"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ទង់ជាតិ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"មិនមាន​រូប​អារម្មណ៍ទេ"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"អ្នក​មិនទាន់​បានប្រើរូប​អារម្មណ៍​ណាមួយ​នៅឡើយទេ"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-kn/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-kn/strings.xml
index 8967259..51131c5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-kn/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-kn/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ವಸ್ತುಗಳು"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ಸಂಕೇತಗಳು"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ಫ್ಲ್ಯಾಗ್‌ಗಳು"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"ಯಾವುದೇ ಎಮೊಜಿಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ko/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ko/strings.xml
index 556d96e..d482493 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ko/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ko/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"사물"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"기호"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"깃발"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"사용 가능한 그림 이모티콘 없음"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ky/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ky/strings.xml
index bf96ba5..cf5d505 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ky/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ky/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ОБЪЕКТТЕР"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"СИМВОЛДОР"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ЖЕЛЕКТЕР"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Жеткиликтүү быйтыкчалар жок"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-lo/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-lo/strings.xml
index 58728bf..32ac1ec 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-lo/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-lo/strings.xml
@@ -27,4 +27,6 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ສັນຍາລັກ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ທຸງ"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"ບໍ່ມີອີໂມຈິໃຫ້ນຳໃຊ້"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
+    <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-lt/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-lt/strings.xml
index 6e243bf..8c90dcb 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-lt/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-lt/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJEKTAI"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOLIAI"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"VĖLIAVOS"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nėra jokių pasiekiamų jaustukų"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-lv/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-lv/strings.xml
index eaae840..c6e30c7 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-lv/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-lv/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJEKTI"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOLI"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"KAROGI"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nav pieejamu emocijzīmju"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml
index 194dbfe..85ac3d7 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"СИМБОЛИ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ЗНАМИЊА"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Нема достапни емоџија"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Сѐ уште не сте користеле емоџија"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ml/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ml/strings.xml
index 6551915..dd073d9 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ml/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ml/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"വസ്‌തുക്കൾ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ചിഹ്നങ്ങൾ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"പതാകകൾ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"ഇമോജികളൊന്നും ലഭ്യമല്ല"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-mn/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-mn/strings.xml
index 0903d6d..53792c9 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-mn/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-mn/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ОБЪЕКТ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ТЭМДЭГ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ТУГ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Боломжтой эможи алга"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Та ямар нэгэн эможи ашиглаагүй байна"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-mr/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-mr/strings.xml
index 133595a..82109e0 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-mr/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-mr/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ऑब्जेक्ट"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"चिन्हे"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ध्वज"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"कोणतेही इमोजी उपलब्ध नाहीत"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ms/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ms/strings.xml
index bbd44a1..1462102 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ms/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ms/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOL"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BENDERA"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Tiada emoji tersedia"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Anda belum menggunakan mana-mana emoji lagi"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-my/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-my/strings.xml
index 22a6327..536c277 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-my/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-my/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"အရာဝတ္ထုများ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"သင်္ကေတများ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"အလံများ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"အီမိုဂျီ မရနိုင်ပါ"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-nb/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-nb/strings.xml
index 600a1de..f050c34 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-nb/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-nb/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"GJENSTANDER"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLER"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAGG"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Ingen emojier er tilgjengelige"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ne/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ne/strings.xml
index 0dfae15..97739a0 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ne/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ne/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"वस्तुहरू"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"चिन्हहरू"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"झन्डाहरू"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"कुनै पनि इमोजी उपलब्ध छैन"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-nl/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-nl/strings.xml
index b662a97..6c9d4c3 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-nl/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-nl/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJECTEN"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLEN"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"VLAGGEN"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Geen emoji\'s beschikbaar"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-or/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-or/strings.xml
index 00cdd48..ae32bb3 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-or/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-or/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ଅବଜେକ୍ଟଗୁଡ଼ିକ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ଚିହ୍ନଗୁଡ଼ିକ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ଫ୍ଲାଗଗୁଡ଼ିକ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"କୌଣସି ଇମୋଜି ଉପଲବ୍ଧ ନାହିଁ"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"ଆପଣ ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଇମୋଜି ବ୍ୟବହାର କରିନାହାଁନ୍ତି"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-pa/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-pa/strings.xml
index 0ee52cb..72e0b6a 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-pa/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-pa/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ਵਸਤੂਆਂ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ਚਿੰਨ੍ਹ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ਝੰਡੇ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"ਕੋਈ ਇਮੋਜੀ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-pl/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-pl/strings.xml
index 427f6be..e4d8310 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-pl/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-pl/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"PRZEDMIOTY"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLE"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAGI"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Brak dostępnych emotikonów"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-pt-rBR/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-pt-rBR/strings.xml
index b8a0e5f..07507fb 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-pt-rBR/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-pt-rBR/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SÍMBOLOS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BANDEIRAS"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Não há emojis disponíveis"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Você ainda não usou emojis"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-pt-rPT/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-pt-rPT/strings.xml
index c6a2bf9..e6e29b1 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-pt-rPT/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-pt-rPT/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SÍMBOLOS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BANDEIRAS"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nenhum emoji disponível"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Ainda não utilizou emojis"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-pt/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-pt/strings.xml
index b8a0e5f..07507fb 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-pt/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-pt/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SÍMBOLOS"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BANDEIRAS"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Não há emojis disponíveis"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Você ainda não usou emojis"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ro/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ro/strings.xml
index 44e6db0..21686b6 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ro/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ro/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBIECTE"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOLURI"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"STEAGURI"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nu sunt disponibile emoji-uri"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Încă nu ai folosit emoji"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ru/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ru/strings.xml
index fec27c9..b7a9857 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ru/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ru/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ОБЪЕКТЫ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"СИМВОЛЫ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ФЛАГИ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Нет доступных эмодзи"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-si/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-si/strings.xml
index cbd141a..e1bfcea 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-si/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-si/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"වස්තු"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"සංකේත"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ධජ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"ඉමොජි කිසිවක් නොලැබේ"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml
index 93a8089..3a557ab 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml
@@ -27,4 +27,6 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLY"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"VLAJKY"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nie sú k dispozícii žiadne emodži"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
+    <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sl/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sl/strings.xml
index 5f8cd6a..91261f4 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sl/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sl/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"PREDMETI"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOLI"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ZASTAVE"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Ni emodžijev"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Uporabili niste še nobenega emodžija."</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sq/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sq/strings.xml
index 91bcd77..532209d 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sq/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sq/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJEKTE"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SIMBOLE"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAMUJ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Nuk ofrohen emoji"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sr/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sr/strings.xml
index f088321..f899101 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sr/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sr/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ОБЈЕКТИ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"СИМБОЛИ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ЗАСТАВЕ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Емоџији нису доступни"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Још нисте користили емоџије"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sv/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sv/strings.xml
index 69b54ae..9ba51c6 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sv/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sv/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"FÖREMÅL"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLER"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAGGOR"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Inga emojier tillgängliga"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml
index 9897f9b..a2b3651 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"VITU"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"ISHARA"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BENDERA"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Hakuna emoji zinazopatikana"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ta/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ta/strings.xml
index f1959f6..6a24080 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ta/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ta/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"பொருட்கள்"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"சின்னங்கள்"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"கொடிகள்"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"ஈமோஜிகள் எதுவுமில்லை"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-te/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-te/strings.xml
index 194ae55..228b252 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-te/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-te/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ఆబ్జెక్ట్‌లు"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"గుర్తులు"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ఫ్లాగ్‌లు"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"ఎమోజీలు ఏవీ అందుబాటులో లేవు"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml
index f6f3782..4718adb 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"สัญลักษณ์"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ธง"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"ไม่มีอีโมจิ"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"คุณยังไม่ได้ใช้อีโมจิเลย"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-tl/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-tl/strings.xml
index b0b0961..d28f9b7 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-tl/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-tl/strings.xml
@@ -26,6 +26,6 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"MGA BAGAY"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"MGA SIMBOLO"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"MGA BANDILA"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
-    <skip />
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Walang available na emoji"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Hindi ka pa gumamit ng anumang emoji"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-tr/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-tr/strings.xml
index a6cddbf..f3dd488 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-tr/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-tr/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"NESNELER"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SEMBOLLER"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BAYRAKLAR"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Kullanılabilir emoji yok"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml
index bb41434..a1d8ff6 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ОБ’ЄКТИ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"СИМВОЛИ"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ПРАПОРИ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Немає смайлів"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ur/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ur/strings.xml
index 5ba39c4..212835f 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ur/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ur/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"آبجیکٹس"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"علامات"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"جھنڈے"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"کوئی بھی ایموجی دستیاب نہیں ہے"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml
index 5107f5e..02613bd 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml
@@ -27,4 +27,5 @@
     <string name="emoji_category_symbols" msgid="5626171724310261787">"BELGILAR"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BAYROQCHALAR"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Hech qanday emoji mavjud emas"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Haligacha birorta emojidan foydalanmagansiz"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-vi/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-vi/strings.xml
index 4583227..baba3bc 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-vi/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-vi/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"ĐỒ VẬT"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"BIỂU TƯỢNG"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"CỜ"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Không có biểu tượng cảm xúc nào"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rCN/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rCN/strings.xml
index 509d61e..385b281 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rCN/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rCN/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"物体"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"符号"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"旗帜"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"没有可用的表情符号"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rHK/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rHK/strings.xml
index ff47ce4..68206d5 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rHK/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rHK/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"物件"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"符號"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"旗幟"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"沒有可用的 Emoji"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rTW/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rTW/strings.xml
index 135da04..7892cef 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-zh-rTW/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-zh-rTW/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"物品"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"符號"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"旗幟"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"沒有可用的表情符號"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-zu/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-zu/strings.xml
index 332f6e4..6c422c9 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-zu/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-zu/strings.xml
@@ -26,6 +26,7 @@
     <string name="emoji_category_objects" msgid="6106115586332708067">"IZINTO"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"AMASIMBULI"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"AMAFULEGI"</string>
-    <!-- no translation found for emoji_empty_non_recent_category (288822832574892625) -->
+    <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Awekho ama-emoji atholakalayo"</string>
+    <!-- no translation found for emoji_empty_recent_category (7863877827879290200) -->
     <skip />
 </resources>
diff --git a/fragment/fragment/api/current.txt b/fragment/fragment/api/current.txt
index d10b6de..76aeea3 100644
--- a/fragment/fragment/api/current.txt
+++ b/fragment/fragment/api/current.txt
@@ -489,6 +489,7 @@
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectSetUserVisibleHint();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectTargetFragmentUsage();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongFragmentContainer();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongNestedHierarchy();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyDeath();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyListener(androidx.fragment.app.strictmode.FragmentStrictMode.OnViolationListener listener);
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyLog();
@@ -539,5 +540,12 @@
     property public final android.view.ViewGroup container;
   }
 
+  public final class WrongNestedHierarchyViolation extends androidx.fragment.app.strictmode.Violation {
+    method public int getContainerId();
+    method public androidx.fragment.app.Fragment getExpectedParentFragment();
+    property public final int containerId;
+    property public final androidx.fragment.app.Fragment expectedParentFragment;
+  }
+
 }
 
diff --git a/fragment/fragment/api/public_plus_experimental_current.txt b/fragment/fragment/api/public_plus_experimental_current.txt
index d10b6de..76aeea3 100644
--- a/fragment/fragment/api/public_plus_experimental_current.txt
+++ b/fragment/fragment/api/public_plus_experimental_current.txt
@@ -489,6 +489,7 @@
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectSetUserVisibleHint();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectTargetFragmentUsage();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongFragmentContainer();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongNestedHierarchy();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyDeath();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyListener(androidx.fragment.app.strictmode.FragmentStrictMode.OnViolationListener listener);
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyLog();
@@ -539,5 +540,12 @@
     property public final android.view.ViewGroup container;
   }
 
+  public final class WrongNestedHierarchyViolation extends androidx.fragment.app.strictmode.Violation {
+    method public int getContainerId();
+    method public androidx.fragment.app.Fragment getExpectedParentFragment();
+    property public final int containerId;
+    property public final androidx.fragment.app.Fragment expectedParentFragment;
+  }
+
 }
 
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index 44528a7..bdcefeb 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -518,6 +518,7 @@
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectSetUserVisibleHint();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectTargetFragmentUsage();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongFragmentContainer();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectWrongNestedHierarchy();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyDeath();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyListener(androidx.fragment.app.strictmode.FragmentStrictMode.OnViolationListener listener);
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyLog();
@@ -568,5 +569,12 @@
     property public final android.view.ViewGroup container;
   }
 
+  public final class WrongNestedHierarchyViolation extends androidx.fragment.app.strictmode.Violation {
+    method public int getContainerId();
+    method public androidx.fragment.app.Fragment getExpectedParentFragment();
+    property public final int containerId;
+    property public final androidx.fragment.app.Fragment expectedParentFragment;
+  }
+
 }
 
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/OnBackPressedCallbackTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/OnBackPressedCallbackTest.kt
index e9428d1..abe5b23 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/OnBackPressedCallbackTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/OnBackPressedCallbackTest.kt
@@ -44,7 +44,8 @@
 
     @Test
     fun testBackPressFinishesActivity() {
-       withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+        // Since this activity finishes manually, we do not want to use withUse here
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
             val countDownLatch = withActivity {
                 onBackPressed()
                 finishCountDownLatch
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/strictmode/FragmentStrictModeTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/strictmode/FragmentStrictModeTest.kt
index 83a2f21..9ea6d81 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/strictmode/FragmentStrictModeTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/strictmode/FragmentStrictModeTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Looper
 import androidx.fragment.app.StrictFragment
+import androidx.fragment.app.StrictViewFragment
 import androidx.fragment.app.executePendingTransactions
 import androidx.fragment.app.test.FragmentTestActivity
 import androidx.fragment.test.R
@@ -234,6 +235,87 @@
         }
     }
 
+    @Test
+    public fun detectWrongNestedHierarchyNoParent() {
+        var violation: Violation? = null
+        val policy = FragmentStrictMode.Policy.Builder()
+            .detectWrongNestedHierarchy()
+            .penaltyListener { violation = it }
+            .build()
+        FragmentStrictMode.defaultPolicy = policy
+
+        withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fm = withActivity {
+                setContentView(R.layout.simple_container)
+                supportFragmentManager
+            }
+            val outerFragment = StrictViewFragment(R.layout.scene1)
+            val innerFragment = StrictViewFragment(R.layout.fragment_a)
+
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, outerFragment)
+                .setReorderingAllowed(false)
+                .commit()
+            // Here we add childFragment to a layout within parentFragment, but we
+            // specifically don't use parentFragment.childFragmentManager
+            fm.beginTransaction()
+                .add(R.id.squareContainer, innerFragment)
+                .setReorderingAllowed(false)
+                .commit()
+            executePendingTransactions()
+
+            assertThat(violation).isInstanceOf(WrongNestedHierarchyViolation::class.java)
+            assertThat(violation).hasMessageThat().contains(
+                "Attempting to nest fragment $innerFragment within the view " +
+                    "of parent fragment $outerFragment via container with ID " +
+                    "${R.id.squareContainer} without using parent's childFragmentManager"
+            )
+        }
+    }
+
+    @Test
+    public fun detectWrongNestedHierarchyWrongParent() {
+        var violation: Violation? = null
+        val policy = FragmentStrictMode.Policy.Builder()
+            .detectWrongNestedHierarchy()
+            .penaltyListener { violation = it }
+            .build()
+        FragmentStrictMode.defaultPolicy = policy
+
+        withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fm = withActivity {
+                setContentView(R.layout.simple_container)
+                supportFragmentManager
+            }
+            val grandParent = StrictViewFragment(R.layout.scene1)
+            val parentFragment = StrictViewFragment(R.layout.scene5)
+            val childFragment = StrictViewFragment(R.layout.fragment_a)
+            fm.beginTransaction()
+                .add(R.id.fragmentContainer, grandParent)
+                .setReorderingAllowed(false)
+                .commit()
+            executePendingTransactions()
+            grandParent.childFragmentManager.beginTransaction()
+                .add(R.id.squareContainer, parentFragment)
+                .setReorderingAllowed(false)
+                .commit()
+            executePendingTransactions()
+            // Here we use the grandParent.childFragmentManager for the child
+            // fragment, though we should actually be using parentFragment.childFragmentManager
+            grandParent.childFragmentManager.beginTransaction()
+                .add(R.id.sharedElementContainer, childFragment)
+                .setReorderingAllowed(false)
+                .commit()
+            executePendingTransactions(parentFragment.childFragmentManager)
+            assertThat(violation).isInstanceOf(WrongNestedHierarchyViolation::class.java)
+            assertThat(violation).hasMessageThat().contains(
+                "Attempting to nest fragment $childFragment within the view " +
+                    "of parent fragment $parentFragment via container with ID " +
+                    "${R.id.sharedElementContainer} without using parent's childFragmentManager"
+            )
+        }
+    }
+
     @Suppress("DEPRECATION")
     @Test
     public fun detectRetainInstanceUsage() {
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 22f4c63..1e0f070 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -1065,7 +1065,7 @@
      * @return the locally scoped {@link Fragment} to the given view, if found
      */
     @Nullable
-    private static Fragment findViewFragment(@NonNull View view) {
+    static Fragment findViewFragment(@NonNull View view) {
         while (view != null) {
             Fragment fragment = getViewFragment(view);
             if (fragment != null) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
index 0b54927..859045b 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java
@@ -860,6 +860,16 @@
     }
 
     void addViewToContainer() {
+        Fragment expectedParent = FragmentManager.findViewFragment(mFragment.mContainer);
+        Fragment actualParent = mFragment.getParentFragment();
+        // onFindViewById prevents any wrong nested hierarchies when expectedParent is null already
+        if (expectedParent != null) {
+            if (!expectedParent.equals(actualParent)) {
+                FragmentStrictMode.onWrongNestedHierarchy(mFragment, expectedParent,
+                        mFragment.mContainerId);
+            }
+        }
+
         // Ensure that our new Fragment is placed in the right index
         // based on its relative position to Fragments already in the
         // same container
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/FragmentStrictMode.kt b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/FragmentStrictMode.kt
index 1137313..9b4eabc 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/FragmentStrictMode.kt
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/FragmentStrictMode.kt
@@ -95,6 +95,27 @@
      */
     @JvmStatic
     @RestrictTo(RestrictTo.Scope.LIBRARY)
+    fun onWrongNestedHierarchy(
+        fragment: Fragment,
+        expectedParentFragment: Fragment,
+        containerId: Int
+    ) {
+        val violation: Violation =
+            WrongNestedHierarchyViolation(fragment, expectedParentFragment, containerId)
+        logIfDebuggingEnabled(violation)
+        val policy = getNearestPolicy(fragment)
+        if (policy.flags.contains(Flag.DETECT_WRONG_NESTED_HIERARCHY) &&
+            shouldHandlePolicyViolation(policy, fragment.javaClass, violation.javaClass)
+        ) {
+            handlePolicyViolation(policy, violation)
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @JvmStatic
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     fun onSetRetainInstanceUsage(fragment: Fragment) {
         val violation: Violation = SetRetainInstanceUsageViolation(fragment)
         logIfDebuggingEnabled(violation)
@@ -284,6 +305,7 @@
         PENALTY_DEATH,
         DETECT_FRAGMENT_REUSE,
         DETECT_FRAGMENT_TAG_USAGE,
+        DETECT_WRONG_NESTED_HIERARCHY,
         DETECT_RETAIN_INSTANCE_USAGE,
         DETECT_SET_USER_VISIBLE_HINT,
         DETECT_TARGET_FRAGMENT_USAGE,
@@ -378,6 +400,13 @@
                 return this
             }
 
+            /** Detects nested fragments that do not use the expected parent's childFragmentManager.  */
+            @SuppressLint("BuilderSetStyle")
+            fun detectWrongNestedHierarchy(): Builder {
+                flags.add(Flag.DETECT_WRONG_NESTED_HIERARCHY)
+                return this
+            }
+
             /**
              * Detects calls to [Fragment.setRetainInstance] and [Fragment.getRetainInstance].
              */
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/WrongNestedHierarchyViolation.kt b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/WrongNestedHierarchyViolation.kt
new file mode 100644
index 0000000..329509f
--- /dev/null
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/WrongNestedHierarchyViolation.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app.strictmode
+
+import androidx.fragment.app.Fragment
+
+/**
+ * See [FragmentStrictMode.Policy.Builder.detectWrongNestedHierarchy].
+ */
+class WrongNestedHierarchyViolation internal constructor(
+    fragment: Fragment,
+    /**
+     * Gets the expected parent [Fragment] of the fragment causing the Violation.
+     */
+    val expectedParentFragment: Fragment,
+    /**
+     * Gets the unique ID of the container that the [Fragment] causing
+     * the Violation would have been added to.
+     */
+    val containerId: Int
+) : Violation(
+    fragment,
+    "Attempting to nest fragment $fragment within the view " +
+        "of parent fragment $expectedParentFragment via container with ID $containerId " +
+        "without using parent's childFragmentManager"
+)
diff --git a/glance/glance-appwidget/build.gradle b/glance/glance-appwidget/build.gradle
index c8aa9d9..1ad5b3b6 100644
--- a/glance/glance-appwidget/build.gradle
+++ b/glance/glance-appwidget/build.gradle
@@ -43,9 +43,9 @@
 
     api(project(":glance:glance"))
     api("androidx.annotation:annotation:1.1.0")
-    api("androidx.compose.runtime:runtime:1.1.0-beta01")
-    api("androidx.compose.ui:ui-graphics:1.1.0-beta01")
-    api("androidx.compose.ui:ui-unit:1.1.0-beta01")
+    api("androidx.compose.runtime:runtime:1.1.1")
+    api("androidx.compose.ui:ui-graphics:1.1.1")
+    api("androidx.compose.ui:ui-unit:1.1.1")
 
     implementation('androidx.core:core-ktx:1.7.0')
     implementation("androidx.datastore:datastore:1.0.0")
diff --git a/glance/glance-appwidget/src/androidMain/res/values-en-rCA/strings.xml b/glance/glance-appwidget/src/androidMain/res/values-en-rCA/strings.xml
index cde96ea..c7ef8be7 100644
--- a/glance/glance-appwidget/src/androidMain/res/values-en-rCA/strings.xml
+++ b/glance/glance-appwidget/src/androidMain/res/values-en-rCA/strings.xml
@@ -17,6 +17,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="glance_error_layout_title" msgid="2896109737634971880"><b>"Glance app widget error"</b></string>
-    <string name="glance_error_layout_text" msgid="8513923302775602854">"Check the exact error using "<b><tt>"adb Logcat"</tt></b>", searching for "<b><tt>"GlanceAppWidget"</tt></b></string>
+    <string name="glance_error_layout_title" msgid="2896109737634971880"><b>"Glance App Widget Error"</b></string>
+    <string name="glance_error_layout_text" msgid="8513923302775602854">"Check the exact error using "<b><tt>"adb logcat"</tt></b>", searching for "<b><tt>"GlanceAppWidget"</tt></b></string>
 </resources>
diff --git a/glance/glance-appwidget/src/main/res/raw/keep.xml b/glance/glance-appwidget/src/main/res/raw/keep.xml
new file mode 100644
index 0000000..481c104
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/raw/keep.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:keep="@layout/root_*"
+    tools:ignore="ResourceName">
+</resources>
+
diff --git a/glance/glance-wear-tiles/build.gradle b/glance/glance-wear-tiles/build.gradle
index 7c09cd6..eccfcf3 100644
--- a/glance/glance-wear-tiles/build.gradle
+++ b/glance/glance-wear-tiles/build.gradle
@@ -30,9 +30,9 @@
 dependencies {
 
     api(project(":glance:glance"))
-    api("androidx.compose.runtime:runtime:1.1.0-beta01")
-    api("androidx.compose.ui:ui-graphics:1.1.0-beta01")
-    api("androidx.compose.ui:ui-unit:1.1.0-beta01")
+    api("androidx.compose.runtime:runtime:1.1.1")
+    api("androidx.compose.ui:ui-graphics:1.1.1")
+    api("androidx.compose.ui:ui-unit:1.1.1")
     api("androidx.wear.tiles:tiles:1.0.0")
 
     implementation(libs.kotlinStdlib)
diff --git a/glance/glance/build.gradle b/glance/glance/build.gradle
index 3d96ae5..ba94622 100644
--- a/glance/glance/build.gradle
+++ b/glance/glance/build.gradle
@@ -30,9 +30,9 @@
 dependencies {
 
     api("androidx.annotation:annotation:1.2.0")
-    api("androidx.compose.runtime:runtime:1.1.0-beta01")
-    api("androidx.compose.ui:ui-graphics:1.1.0-beta01")
-    api("androidx.compose.ui:ui-unit:1.1.0-beta01")
+    api("androidx.compose.runtime:runtime:1.1.1")
+    api("androidx.compose.ui:ui-graphics:1.1.1")
+    api("androidx.compose.ui:ui-unit:1.1.1")
     api("androidx.datastore:datastore-core:1.0.0")
     api("androidx.datastore:datastore-preferences-core:1.0.0")
     api("androidx.datastore:datastore-preferences:1.0.0")
diff --git a/gradle.properties b/gradle.properties
index 14b6286..c6660ac 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -23,6 +23,9 @@
 # Don't generate versioned API files
 androidx.writeVersionedApiFiles=true
 
+# Do restrict compileSdkPreview usage
+androidx.allowCustomCompileSdk=false
+
 # Don't warn about needing to update AGP
 android.suppressUnsupportedCompileSdk=Tiramisu,33
 
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/UpdateExerciseTypeConfigRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/UpdateExerciseTypeConfigRequest.kt
index 584f05a..a3820b5 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/UpdateExerciseTypeConfigRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/UpdateExerciseTypeConfigRequest.kt
@@ -18,7 +18,6 @@
 
 import android.os.Parcelable
 import androidx.annotation.RestrictTo
-import androidx.health.services.client.data.GolfShotTrackingPlaceInfo
 import androidx.health.services.client.data.ExerciseTypeConfig
 import androidx.health.services.client.data.ProtoParcelable
 import androidx.health.services.client.proto.RequestsProto
@@ -43,11 +42,7 @@
         @JvmField
         val CREATOR: Parcelable.Creator<UpdateExerciseTypeConfigRequest> = newCreator { bytes ->
             val proto = RequestsProto.UpdateExerciseTypeConfigRequest.parseFrom(bytes)
-            UpdateExerciseTypeConfigRequest(
-                proto.packageName,
-                ExerciseTypeConfig.createGolfExerciseTypeConfig(
-                    GolfShotTrackingPlaceInfo.fromProto(proto.config.golfShotTrackingPlaceInfo))
-                )
+            UpdateExerciseTypeConfigRequest(proto.packageName, ExerciseTypeConfig(proto.config))
         }
     }
 }
\ No newline at end of file
diff --git a/heifwriter/heifwriter/src/androidTest/java/androidx/heifwriter/HeifWriterTest.java b/heifwriter/heifwriter/src/androidTest/java/androidx/heifwriter/HeifWriterTest.java
index af40a5a..b8e3752 100644
--- a/heifwriter/heifwriter/src/androidTest/java/androidx/heifwriter/HeifWriterTest.java
+++ b/heifwriter/heifwriter/src/androidTest/java/androidx/heifwriter/HeifWriterTest.java
@@ -53,6 +53,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -157,6 +158,7 @@
         }
     }
 
+    @Ignore // b/239415930
     @Test
     @LargeTest
     public void testInputBuffer_NoGrid_NoHandler() throws Throwable {
@@ -166,6 +168,7 @@
         doTestForVariousNumberImages(builder);
     }
 
+    @Ignore // b/239415930
     @Test
     @LargeTest
     public void testInputBuffer_Grid_NoHandler() throws Throwable {
@@ -175,6 +178,7 @@
         doTestForVariousNumberImages(builder);
     }
 
+    @Ignore // b/239415930
     @Test
     @LargeTest
     public void testInputBuffer_NoGrid_Handler() throws Throwable {
@@ -184,6 +188,7 @@
         doTestForVariousNumberImages(builder);
     }
 
+    @Ignore // b/239415930
     @Test
     @LargeTest
     public void testInputBuffer_Grid_Handler() throws Throwable {
diff --git a/lifecycle/integration-tests/kotlintestapp/src/test-common/java/androidx.lifecycle/LifecycleCoroutineScopeTestBase.kt b/lifecycle/integration-tests/kotlintestapp/src/test-common/java/androidx.lifecycle/LifecycleCoroutineScopeTestBase.kt
index 43a6a72..5b4d60d 100644
--- a/lifecycle/integration-tests/kotlintestapp/src/test-common/java/androidx.lifecycle/LifecycleCoroutineScopeTestBase.kt
+++ b/lifecycle/integration-tests/kotlintestapp/src/test-common/java/androidx.lifecycle/LifecycleCoroutineScopeTestBase.kt
@@ -36,7 +36,7 @@
     fun initialization() {
         val owner = TestLifecycleOwner(Lifecycle.State.INITIALIZED, UnconfinedTestDispatcher())
         val scope = owner.lifecycleScope
-        assertThat(owner.lifecycle.mInternalScopeRef.get()).isSameInstanceAs(scope)
+        assertThat(owner.lifecycle.internalScopeRef.get()).isSameInstanceAs(scope)
         val scope2 = owner.lifecycleScope
         assertThat(scope).isSameInstanceAs(scope2)
         runBlocking(Dispatchers.Main) {
diff --git a/lifecycle/lifecycle-common/api/current.txt b/lifecycle/lifecycle-common/api/current.txt
index 53075b6..a339f50 100644
--- a/lifecycle/lifecycle-common/api/current.txt
+++ b/lifecycle/lifecycle-common/api/current.txt
@@ -12,17 +12,21 @@
 
   public abstract class Lifecycle {
     ctor public Lifecycle();
-    method @MainThread public abstract void addObserver(androidx.lifecycle.LifecycleObserver);
+    method @MainThread public abstract void addObserver(androidx.lifecycle.LifecycleObserver observer);
     method @MainThread public abstract androidx.lifecycle.Lifecycle.State getCurrentState();
-    method @MainThread public abstract void removeObserver(androidx.lifecycle.LifecycleObserver);
+    method @MainThread public abstract void removeObserver(androidx.lifecycle.LifecycleObserver observer);
+    property @MainThread public abstract androidx.lifecycle.Lifecycle.State currentState;
   }
 
   public enum Lifecycle.Event {
-    method public static androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State);
-    method public static androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State);
-    method public androidx.lifecycle.Lifecycle.State getTargetState();
-    method public static androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State);
-    method public static androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State);
+    method public static final androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State state);
+    method public static final androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State state);
+    method public final androidx.lifecycle.Lifecycle.State getTargetState();
+    method public static final androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State state);
+    method public static final androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State state);
+    method public static androidx.lifecycle.Lifecycle.Event valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.lifecycle.Lifecycle.Event[] values();
+    property public final androidx.lifecycle.Lifecycle.State targetState;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_ANY;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_CREATE;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_DESTROY;
@@ -30,10 +34,20 @@
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_RESUME;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_START;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_STOP;
+    field public static final androidx.lifecycle.Lifecycle.Event.Companion Companion;
+  }
+
+  public static final class Lifecycle.Event.Companion {
+    method public androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State state);
+    method public androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State state);
+    method public androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State state);
+    method public androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State state);
   }
 
   public enum Lifecycle.State {
-    method public boolean isAtLeast(androidx.lifecycle.Lifecycle.State);
+    method public final boolean isAtLeast(androidx.lifecycle.Lifecycle.State state);
+    method public static androidx.lifecycle.Lifecycle.State valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.lifecycle.Lifecycle.State[] values();
     enum_constant public static final androidx.lifecycle.Lifecycle.State CREATED;
     enum_constant public static final androidx.lifecycle.Lifecycle.State DESTROYED;
     enum_constant public static final androidx.lifecycle.Lifecycle.State INITIALIZED;
diff --git a/lifecycle/lifecycle-common/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-common/api/public_plus_experimental_current.txt
index 53075b6..a339f50 100644
--- a/lifecycle/lifecycle-common/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-common/api/public_plus_experimental_current.txt
@@ -12,17 +12,21 @@
 
   public abstract class Lifecycle {
     ctor public Lifecycle();
-    method @MainThread public abstract void addObserver(androidx.lifecycle.LifecycleObserver);
+    method @MainThread public abstract void addObserver(androidx.lifecycle.LifecycleObserver observer);
     method @MainThread public abstract androidx.lifecycle.Lifecycle.State getCurrentState();
-    method @MainThread public abstract void removeObserver(androidx.lifecycle.LifecycleObserver);
+    method @MainThread public abstract void removeObserver(androidx.lifecycle.LifecycleObserver observer);
+    property @MainThread public abstract androidx.lifecycle.Lifecycle.State currentState;
   }
 
   public enum Lifecycle.Event {
-    method public static androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State);
-    method public static androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State);
-    method public androidx.lifecycle.Lifecycle.State getTargetState();
-    method public static androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State);
-    method public static androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State);
+    method public static final androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State state);
+    method public static final androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State state);
+    method public final androidx.lifecycle.Lifecycle.State getTargetState();
+    method public static final androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State state);
+    method public static final androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State state);
+    method public static androidx.lifecycle.Lifecycle.Event valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.lifecycle.Lifecycle.Event[] values();
+    property public final androidx.lifecycle.Lifecycle.State targetState;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_ANY;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_CREATE;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_DESTROY;
@@ -30,10 +34,20 @@
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_RESUME;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_START;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_STOP;
+    field public static final androidx.lifecycle.Lifecycle.Event.Companion Companion;
+  }
+
+  public static final class Lifecycle.Event.Companion {
+    method public androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State state);
+    method public androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State state);
+    method public androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State state);
+    method public androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State state);
   }
 
   public enum Lifecycle.State {
-    method public boolean isAtLeast(androidx.lifecycle.Lifecycle.State);
+    method public final boolean isAtLeast(androidx.lifecycle.Lifecycle.State state);
+    method public static androidx.lifecycle.Lifecycle.State valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.lifecycle.Lifecycle.State[] values();
     enum_constant public static final androidx.lifecycle.Lifecycle.State CREATED;
     enum_constant public static final androidx.lifecycle.Lifecycle.State DESTROYED;
     enum_constant public static final androidx.lifecycle.Lifecycle.State INITIALIZED;
diff --git a/lifecycle/lifecycle-common/api/restricted_current.txt b/lifecycle/lifecycle-common/api/restricted_current.txt
index e8ec1e4..045594a 100644
--- a/lifecycle/lifecycle-common/api/restricted_current.txt
+++ b/lifecycle/lifecycle-common/api/restricted_current.txt
@@ -19,17 +19,21 @@
 
   public abstract class Lifecycle {
     ctor public Lifecycle();
-    method @MainThread public abstract void addObserver(androidx.lifecycle.LifecycleObserver);
+    method @MainThread public abstract void addObserver(androidx.lifecycle.LifecycleObserver observer);
     method @MainThread public abstract androidx.lifecycle.Lifecycle.State getCurrentState();
-    method @MainThread public abstract void removeObserver(androidx.lifecycle.LifecycleObserver);
+    method @MainThread public abstract void removeObserver(androidx.lifecycle.LifecycleObserver observer);
+    property @MainThread public abstract androidx.lifecycle.Lifecycle.State currentState;
   }
 
   public enum Lifecycle.Event {
-    method public static androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State);
-    method public static androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State);
-    method public androidx.lifecycle.Lifecycle.State getTargetState();
-    method public static androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State);
-    method public static androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State);
+    method public static final androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State state);
+    method public static final androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State state);
+    method public final androidx.lifecycle.Lifecycle.State getTargetState();
+    method public static final androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State state);
+    method public static final androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State state);
+    method public static androidx.lifecycle.Lifecycle.Event valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.lifecycle.Lifecycle.Event[] values();
+    property public final androidx.lifecycle.Lifecycle.State targetState;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_ANY;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_CREATE;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_DESTROY;
@@ -37,10 +41,20 @@
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_RESUME;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_START;
     enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_STOP;
+    field public static final androidx.lifecycle.Lifecycle.Event.Companion Companion;
+  }
+
+  public static final class Lifecycle.Event.Companion {
+    method public androidx.lifecycle.Lifecycle.Event? downFrom(androidx.lifecycle.Lifecycle.State state);
+    method public androidx.lifecycle.Lifecycle.Event? downTo(androidx.lifecycle.Lifecycle.State state);
+    method public androidx.lifecycle.Lifecycle.Event? upFrom(androidx.lifecycle.Lifecycle.State state);
+    method public androidx.lifecycle.Lifecycle.Event? upTo(androidx.lifecycle.Lifecycle.State state);
   }
 
   public enum Lifecycle.State {
-    method public boolean isAtLeast(androidx.lifecycle.Lifecycle.State);
+    method public final boolean isAtLeast(androidx.lifecycle.Lifecycle.State state);
+    method public static androidx.lifecycle.Lifecycle.State valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.lifecycle.Lifecycle.State[] values();
     enum_constant public static final androidx.lifecycle.Lifecycle.State CREATED;
     enum_constant public static final androidx.lifecycle.Lifecycle.State DESTROYED;
     enum_constant public static final androidx.lifecycle.Lifecycle.State INITIALIZED;
diff --git a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycle.java b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycle.java
deleted file mode 100644
index 0a5efd9..0000000
--- a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycle.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Defines an object that has an Android Lifecycle. {@link androidx.fragment.app.Fragment Fragment}
- * and {@link androidx.fragment.app.FragmentActivity FragmentActivity} classes implement
- * {@link LifecycleOwner} interface which has the {@link LifecycleOwner#getLifecycle()
- * getLifecycle} method to access the Lifecycle. You can also implement {@link LifecycleOwner}
- * in your own classes.
- * <p>
- * {@link Event#ON_CREATE}, {@link Event#ON_START}, {@link Event#ON_RESUME} events in this class
- * are dispatched <b>after</b> the {@link LifecycleOwner}'s related method returns.
- * {@link Event#ON_PAUSE}, {@link Event#ON_STOP}, {@link Event#ON_DESTROY} events in this class
- * are dispatched <b>before</b> the {@link LifecycleOwner}'s related method is called.
- * For instance, {@link Event#ON_START} will be dispatched after
- * {@link android.app.Activity#onStart onStart} returns, {@link Event#ON_STOP} will be dispatched
- * before {@link android.app.Activity#onStop onStop} is called.
- * This gives you certain guarantees on which state the owner is in.
- * <p>
- * To observe lifecycle events call {@link #addObserver(LifecycleObserver)} passing an object
- * that implements either {@link DefaultLifecycleObserver} or {@link LifecycleEventObserver}.
- */
-public abstract class Lifecycle {
-
-    /**
-     * Lifecycle coroutines extensions stashes the CoroutineScope into this field.
-     *
-     * @hide used by lifecycle-common-ktx
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @NonNull
-    AtomicReference<Object> mInternalScopeRef = new AtomicReference<>();
-
-    /**
-     * Adds a LifecycleObserver that will be notified when the LifecycleOwner changes
-     * state.
-     * <p>
-     * The given observer will be brought to the current state of the LifecycleOwner.
-     * For example, if the LifecycleOwner is in {@link State#STARTED} state, the given observer
-     * will receive {@link Event#ON_CREATE}, {@link Event#ON_START} events.
-     *
-     * @param observer The observer to notify.
-     */
-    @MainThread
-    public abstract void addObserver(@NonNull LifecycleObserver observer);
-
-    /**
-     * Removes the given observer from the observers list.
-     * <p>
-     * If this method is called while a state change is being dispatched,
-     * <ul>
-     * <li>If the given observer has not yet received that event, it will not receive it.
-     * <li>If the given observer has more than 1 method that observes the currently dispatched
-     * event and at least one of them received the event, all of them will receive the event and
-     * the removal will happen afterwards.
-     * </ul>
-     *
-     * @param observer The observer to be removed.
-     */
-    @MainThread
-    public abstract void removeObserver(@NonNull LifecycleObserver observer);
-
-    /**
-     * Returns the current state of the Lifecycle.
-     *
-     * @return The current state of the Lifecycle.
-     */
-    @MainThread
-    @NonNull
-    public abstract State getCurrentState();
-
-    @SuppressWarnings("WeakerAccess")
-    public enum Event {
-        /**
-         * Constant for onCreate event of the {@link LifecycleOwner}.
-         */
-        ON_CREATE,
-        /**
-         * Constant for onStart event of the {@link LifecycleOwner}.
-         */
-        ON_START,
-        /**
-         * Constant for onResume event of the {@link LifecycleOwner}.
-         */
-        ON_RESUME,
-        /**
-         * Constant for onPause event of the {@link LifecycleOwner}.
-         */
-        ON_PAUSE,
-        /**
-         * Constant for onStop event of the {@link LifecycleOwner}.
-         */
-        ON_STOP,
-        /**
-         * Constant for onDestroy event of the {@link LifecycleOwner}.
-         */
-        ON_DESTROY,
-        /**
-         * An {@link Event Event} constant that can be used to match all events.
-         */
-        ON_ANY;
-
-        /**
-         * Returns the {@link Lifecycle.Event} that will be reported by a {@link Lifecycle}
-         * leaving the specified {@link Lifecycle.State} to a lower state, or {@code null}
-         * if there is no valid event that can move down from the given state.
-         *
-         * @param state the higher state that the returned event will transition down from
-         * @return the event moving down the lifecycle phases from state
-         */
-        @Nullable
-        public static Event downFrom(@NonNull State state) {
-            switch (state) {
-                case CREATED:
-                    return ON_DESTROY;
-                case STARTED:
-                    return ON_STOP;
-                case RESUMED:
-                    return ON_PAUSE;
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Returns the {@link Lifecycle.Event} that will be reported by a {@link Lifecycle}
-         * entering the specified {@link Lifecycle.State} from a higher state, or {@code null}
-         * if there is no valid event that can move down to the given state.
-         *
-         * @param state the lower state that the returned event will transition down to
-         * @return the event moving down the lifecycle phases to state
-         */
-        @Nullable
-        public static Event downTo(@NonNull State state) {
-            switch (state) {
-                case DESTROYED:
-                    return ON_DESTROY;
-                case CREATED:
-                    return ON_STOP;
-                case STARTED:
-                    return ON_PAUSE;
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Returns the {@link Lifecycle.Event} that will be reported by a {@link Lifecycle}
-         * leaving the specified {@link Lifecycle.State} to a higher state, or {@code null}
-         * if there is no valid event that can move up from the given state.
-         *
-         * @param state the lower state that the returned event will transition up from
-         * @return the event moving up the lifecycle phases from state
-         */
-        @Nullable
-        public static Event upFrom(@NonNull State state) {
-            switch (state) {
-                case INITIALIZED:
-                    return ON_CREATE;
-                case CREATED:
-                    return ON_START;
-                case STARTED:
-                    return ON_RESUME;
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Returns the {@link Lifecycle.Event} that will be reported by a {@link Lifecycle}
-         * entering the specified {@link Lifecycle.State} from a lower state, or {@code null}
-         * if there is no valid event that can move up to the given state.
-         *
-         * @param state the higher state that the returned event will transition up to
-         * @return the event moving up the lifecycle phases to state
-         */
-        @Nullable
-        public static Event upTo(@NonNull State state) {
-            switch (state) {
-                case CREATED:
-                    return ON_CREATE;
-                case STARTED:
-                    return ON_START;
-                case RESUMED:
-                    return ON_RESUME;
-                default:
-                    return null;
-            }
-        }
-
-        /**
-         * Returns the new {@link Lifecycle.State} of a {@link Lifecycle} that just reported
-         * this {@link Lifecycle.Event}.
-         *
-         * Throws {@link IllegalArgumentException} if called on {@link #ON_ANY}, as it is a special
-         * value used by {@link OnLifecycleEvent} and not a real lifecycle event.
-         *
-         * @return the state that will result from this event
-         */
-        @NonNull
-        public State getTargetState() {
-            switch (this) {
-                case ON_CREATE:
-                case ON_STOP:
-                    return State.CREATED;
-                case ON_START:
-                case ON_PAUSE:
-                    return State.STARTED;
-                case ON_RESUME:
-                    return State.RESUMED;
-                case ON_DESTROY:
-                    return State.DESTROYED;
-                case ON_ANY:
-                    break;
-            }
-            throw new IllegalArgumentException(this + " has no target state");
-        }
-    }
-
-    /**
-     * Lifecycle states. You can consider the states as the nodes in a graph and
-     * {@link Event}s as the edges between these nodes.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public enum State {
-        /**
-         * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
-         * any more events. For instance, for an {@link android.app.Activity}, this state is reached
-         * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
-         */
-        DESTROYED,
-
-        /**
-         * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
-         * the state when it is constructed but has not received
-         * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
-         */
-        INITIALIZED,
-
-        /**
-         * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
-         * is reached in two cases:
-         * <ul>
-         *     <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
-         *     <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
-         * </ul>
-         */
-        CREATED,
-
-        /**
-         * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
-         * is reached in two cases:
-         * <ul>
-         *     <li>after {@link android.app.Activity#onStart() onStart} call;
-         *     <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
-         * </ul>
-         */
-        STARTED,
-
-        /**
-         * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
-         * is reached after {@link android.app.Activity#onResume() onResume} is called.
-         */
-        RESUMED;
-
-        /**
-         * Compares if this State is greater or equal to the given {@code state}.
-         *
-         * @param state State to compare with
-         * @return true if this State is greater or equal to the given {@code state}
-         */
-        public boolean isAtLeast(@NonNull State state) {
-            return compareTo(state) >= 0;
-        }
-    }
-}
diff --git a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycle.kt b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycle.kt
new file mode 100644
index 0000000..a0e6648
--- /dev/null
+++ b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/Lifecycle.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2017 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.lifecycle
+
+import androidx.annotation.MainThread
+import androidx.annotation.RestrictTo
+import androidx.lifecycle.Lifecycle.Event
+import java.util.concurrent.atomic.AtomicReference
+
+/**
+ * Defines an object that has an Android Lifecycle. [Fragment][androidx.fragment.app.Fragment]
+ * and [FragmentActivity][androidx.fragment.app.FragmentActivity] classes implement
+ * [LifecycleOwner] interface which has the [ getLifecycle][LifecycleOwner.getLifecycle] method to access the Lifecycle. You can also implement [LifecycleOwner]
+ * in your own classes.
+ *
+ * [Event.ON_CREATE], [Event.ON_START], [Event.ON_RESUME] events in this class
+ * are dispatched **after** the [LifecycleOwner]'s related method returns.
+ * [Event.ON_PAUSE], [Event.ON_STOP], [Event.ON_DESTROY] events in this class
+ * are dispatched **before** the [LifecycleOwner]'s related method is called.
+ * For instance, [Event.ON_START] will be dispatched after
+ * [onStart][android.app.Activity.onStart] returns, [Event.ON_STOP] will be dispatched
+ * before [onStop][android.app.Activity.onStop] is called.
+ * This gives you certain guarantees on which state the owner is in.
+ *
+ * To observe lifecycle events call [.addObserver] passing an object
+ * that implements either [DefaultLifecycleObserver] or [LifecycleEventObserver].
+ */
+public abstract class Lifecycle {
+    /**
+     * Lifecycle coroutines extensions stashes the CoroutineScope into this field.
+     *
+     * @hide used by lifecycle-common-ktx
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public var internalScopeRef: AtomicReference<Any> = AtomicReference<Any>()
+
+    /**
+     * Adds a LifecycleObserver that will be notified when the LifecycleOwner changes
+     * state.
+     *
+     * The given observer will be brought to the current state of the LifecycleOwner.
+     * For example, if the LifecycleOwner is in [State.STARTED] state, the given observer
+     * will receive [Event.ON_CREATE], [Event.ON_START] events.
+     *
+     * @param observer The observer to notify.
+     */
+    @MainThread
+    public abstract fun addObserver(observer: LifecycleObserver)
+
+    /**
+     * Removes the given observer from the observers list.
+     *
+     * If this method is called while a state change is being dispatched,
+     *
+     *  * If the given observer has not yet received that event, it will not receive it.
+     *  * If the given observer has more than 1 method that observes the currently dispatched
+     * event and at least one of them received the event, all of them will receive the event and
+     * the removal will happen afterwards.
+     *
+     *
+     * @param observer The observer to be removed.
+     */
+    @MainThread
+    public abstract fun removeObserver(observer: LifecycleObserver)
+
+    /**
+     * Returns the current state of the Lifecycle.
+     *
+     * @return The current state of the Lifecycle.
+     */
+    @get:MainThread
+    public abstract val currentState: State
+
+    public enum class Event {
+        /**
+         * Constant for onCreate event of the [LifecycleOwner].
+         */
+        ON_CREATE,
+
+        /**
+         * Constant for onStart event of the [LifecycleOwner].
+         */
+        ON_START,
+
+        /**
+         * Constant for onResume event of the [LifecycleOwner].
+         */
+        ON_RESUME,
+
+        /**
+         * Constant for onPause event of the [LifecycleOwner].
+         */
+        ON_PAUSE,
+
+        /**
+         * Constant for onStop event of the [LifecycleOwner].
+         */
+        ON_STOP,
+
+        /**
+         * Constant for onDestroy event of the [LifecycleOwner].
+         */
+        ON_DESTROY,
+
+        /**
+         * An [Event] constant that can be used to match all events.
+         */
+        ON_ANY;
+
+        /**
+         * Returns the new [Lifecycle.State] of a [Lifecycle] that just reported
+         * this [Lifecycle.Event].
+         *
+         * Throws [IllegalArgumentException] if called on [.ON_ANY], as it is a special
+         * value used by [OnLifecycleEvent] and not a real lifecycle event.
+         *
+         * @return the state that will result from this event
+         */
+        public val targetState: State
+            get() {
+                when (this) {
+                    ON_CREATE, ON_STOP -> return State.CREATED
+                    ON_START, ON_PAUSE -> return State.STARTED
+                    ON_RESUME -> return State.RESUMED
+                    ON_DESTROY -> return State.DESTROYED
+                    ON_ANY -> {}
+                }
+                throw IllegalArgumentException("$this has no target state")
+            }
+
+        public companion object {
+            /**
+             * Returns the [Lifecycle.Event] that will be reported by a [Lifecycle]
+             * leaving the specified [Lifecycle.State] to a lower state, or `null`
+             * if there is no valid event that can move down from the given state.
+             *
+             * @param state the higher state that the returned event will transition down from
+             * @return the event moving down the lifecycle phases from state
+             */
+            @JvmStatic
+            public fun downFrom(state: State): Event? {
+                return when (state) {
+                    State.CREATED -> ON_DESTROY
+                    State.STARTED -> ON_STOP
+                    State.RESUMED -> ON_PAUSE
+                    else -> null
+                }
+            }
+
+            /**
+             * Returns the [Lifecycle.Event] that will be reported by a [Lifecycle]
+             * entering the specified [Lifecycle.State] from a higher state, or `null`
+             * if there is no valid event that can move down to the given state.
+             *
+             * @param state the lower state that the returned event will transition down to
+             * @return the event moving down the lifecycle phases to state
+             */
+            @JvmStatic
+            public fun downTo(state: State): Event? {
+                return when (state) {
+                    State.DESTROYED -> ON_DESTROY
+                    State.CREATED -> ON_STOP
+                    State.STARTED -> ON_PAUSE
+                    else -> null
+                }
+            }
+
+            /**
+             * Returns the [Lifecycle.Event] that will be reported by a [Lifecycle]
+             * leaving the specified [Lifecycle.State] to a higher state, or `null`
+             * if there is no valid event that can move up from the given state.
+             *
+             * @param state the lower state that the returned event will transition up from
+             * @return the event moving up the lifecycle phases from state
+             */
+            @JvmStatic
+            public fun upFrom(state: State): Event? {
+                return when (state) {
+                    State.INITIALIZED -> ON_CREATE
+                    State.CREATED -> ON_START
+                    State.STARTED -> ON_RESUME
+                    else -> null
+                }
+            }
+
+            /**
+             * Returns the [Lifecycle.Event] that will be reported by a [Lifecycle]
+             * entering the specified [Lifecycle.State] from a lower state, or `null`
+             * if there is no valid event that can move up to the given state.
+             *
+             * @param state the higher state that the returned event will transition up to
+             * @return the event moving up the lifecycle phases to state
+             */
+            @JvmStatic
+            public fun upTo(state: State): Event? {
+                return when (state) {
+                    State.CREATED -> ON_CREATE
+                    State.STARTED -> ON_START
+                    State.RESUMED -> ON_RESUME
+                    else -> null
+                }
+            }
+        }
+    }
+
+    /**
+     * Lifecycle states. You can consider the states as the nodes in a graph and
+     * [Event]s as the edges between these nodes.
+     */
+    public enum class State {
+        /**
+         * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
+         * any more events. For instance, for an [android.app.Activity], this state is reached
+         * **right before** Activity's [onDestroy][android.app.Activity.onDestroy] call.
+         */
+        DESTROYED,
+
+        /**
+         * Initialized state for a LifecycleOwner. For an [android.app.Activity], this is
+         * the state when it is constructed but has not received
+         * [onCreate][android.app.Activity.onCreate] yet.
+         */
+        INITIALIZED,
+
+        /**
+         * Created state for a LifecycleOwner. For an [android.app.Activity], this state
+         * is reached in two cases:
+         *
+         *  * after [onCreate][android.app.Activity.onCreate] call;
+         *  * **right before** [onStop][android.app.Activity.onStop] call.
+         *
+         */
+        CREATED,
+
+        /**
+         * Started state for a LifecycleOwner. For an [android.app.Activity], this state
+         * is reached in two cases:
+         *
+         *  * after [onStart][android.app.Activity.onStart] call;
+         *  * **right before** [onPause][android.app.Activity.onPause] call.
+         *
+         */
+        STARTED,
+
+        /**
+         * Resumed state for a LifecycleOwner. For an [android.app.Activity], this state
+         * is reached after [onResume][android.app.Activity.onResume] is called.
+         */
+        RESUMED;
+
+        /**
+         * Compares if this State is greater or equal to the given `state`.
+         *
+         * @param state State to compare with
+         * @return true if this State is greater or equal to the given `state`
+         */
+        public fun isAtLeast(state: State): Boolean {
+            return compareTo(state) >= 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/SingleGeneratedAdapterObserver.java b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/SingleGeneratedAdapterObserver.java
deleted file mode 100644
index 13f7b63..0000000
--- a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/SingleGeneratedAdapterObserver.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle;
-
-import androidx.annotation.NonNull;
-
-class SingleGeneratedAdapterObserver implements LifecycleEventObserver {
-
-    private final GeneratedAdapter mGeneratedAdapter;
-
-    SingleGeneratedAdapterObserver(GeneratedAdapter generatedAdapter) {
-        mGeneratedAdapter = generatedAdapter;
-    }
-
-    @Override
-    public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
-        mGeneratedAdapter.callMethods(source, event, false, null);
-        mGeneratedAdapter.callMethods(source, event, true, null);
-    }
-}
diff --git a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/SingleGeneratedAdapterObserver.kt b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/SingleGeneratedAdapterObserver.kt
new file mode 100644
index 0000000..e7c40d8
--- /dev/null
+++ b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/SingleGeneratedAdapterObserver.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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.lifecycle
+
+internal class SingleGeneratedAdapterObserver(
+    private val generatedAdapter: GeneratedAdapter
+) : LifecycleEventObserver {
+    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+        generatedAdapter.callMethods(source, event, false, null)
+        generatedAdapter.callMethods(source, event, true, null)
+    }
+}
diff --git a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/Lifecycle.kt b/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/Lifecycle.kt
index 1bb682c..028af8a 100644
--- a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/Lifecycle.kt
+++ b/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/Lifecycle.kt
@@ -35,7 +35,7 @@
 public val Lifecycle.coroutineScope: LifecycleCoroutineScope
     get() {
         while (true) {
-            val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
+            val existing = internalScopeRef.get() as LifecycleCoroutineScopeImpl?
             if (existing != null) {
                 return existing
             }
@@ -43,7 +43,7 @@
                 this,
                 SupervisorJob() + Dispatchers.Main.immediate
             )
-            if (mInternalScopeRef.compareAndSet(null, newScope)) {
+            if (internalScopeRef.compareAndSet(null, newScope)) {
                 newScope.register()
                 return newScope
             }
diff --git a/lifecycle/lifecycle-runtime/api/current.txt b/lifecycle/lifecycle-runtime/api/current.txt
index 5bbe95f..c7da237 100644
--- a/lifecycle/lifecycle-runtime/api/current.txt
+++ b/lifecycle/lifecycle-runtime/api/current.txt
@@ -2,15 +2,22 @@
 package androidx.lifecycle {
 
   public class LifecycleRegistry extends androidx.lifecycle.Lifecycle {
-    ctor public LifecycleRegistry(androidx.lifecycle.LifecycleOwner);
-    method public void addObserver(androidx.lifecycle.LifecycleObserver);
-    method @VisibleForTesting public static androidx.lifecycle.LifecycleRegistry createUnsafe(androidx.lifecycle.LifecycleOwner);
+    ctor public LifecycleRegistry(androidx.lifecycle.LifecycleOwner provider);
+    method public void addObserver(androidx.lifecycle.LifecycleObserver observer);
+    method @VisibleForTesting public static final androidx.lifecycle.LifecycleRegistry createUnsafe(androidx.lifecycle.LifecycleOwner owner);
     method public androidx.lifecycle.Lifecycle.State getCurrentState();
     method public int getObserverCount();
-    method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event);
-    method @Deprecated @MainThread public void markState(androidx.lifecycle.Lifecycle.State);
-    method public void removeObserver(androidx.lifecycle.LifecycleObserver);
-    method @MainThread public void setCurrentState(androidx.lifecycle.Lifecycle.State);
+    method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event event);
+    method @Deprecated @MainThread public void markState(androidx.lifecycle.Lifecycle.State state);
+    method public void removeObserver(androidx.lifecycle.LifecycleObserver observer);
+    method public void setCurrentState(androidx.lifecycle.Lifecycle.State);
+    property public androidx.lifecycle.Lifecycle.State currentState;
+    property public int observerCount;
+    field public static final androidx.lifecycle.LifecycleRegistry.Companion Companion;
+  }
+
+  public static final class LifecycleRegistry.Companion {
+    method @VisibleForTesting public androidx.lifecycle.LifecycleRegistry createUnsafe(androidx.lifecycle.LifecycleOwner owner);
   }
 
   @Deprecated public interface LifecycleRegistryOwner extends androidx.lifecycle.LifecycleOwner {
diff --git a/lifecycle/lifecycle-runtime/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-runtime/api/public_plus_experimental_current.txt
index 5bbe95f..c7da237 100644
--- a/lifecycle/lifecycle-runtime/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-runtime/api/public_plus_experimental_current.txt
@@ -2,15 +2,22 @@
 package androidx.lifecycle {
 
   public class LifecycleRegistry extends androidx.lifecycle.Lifecycle {
-    ctor public LifecycleRegistry(androidx.lifecycle.LifecycleOwner);
-    method public void addObserver(androidx.lifecycle.LifecycleObserver);
-    method @VisibleForTesting public static androidx.lifecycle.LifecycleRegistry createUnsafe(androidx.lifecycle.LifecycleOwner);
+    ctor public LifecycleRegistry(androidx.lifecycle.LifecycleOwner provider);
+    method public void addObserver(androidx.lifecycle.LifecycleObserver observer);
+    method @VisibleForTesting public static final androidx.lifecycle.LifecycleRegistry createUnsafe(androidx.lifecycle.LifecycleOwner owner);
     method public androidx.lifecycle.Lifecycle.State getCurrentState();
     method public int getObserverCount();
-    method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event);
-    method @Deprecated @MainThread public void markState(androidx.lifecycle.Lifecycle.State);
-    method public void removeObserver(androidx.lifecycle.LifecycleObserver);
-    method @MainThread public void setCurrentState(androidx.lifecycle.Lifecycle.State);
+    method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event event);
+    method @Deprecated @MainThread public void markState(androidx.lifecycle.Lifecycle.State state);
+    method public void removeObserver(androidx.lifecycle.LifecycleObserver observer);
+    method public void setCurrentState(androidx.lifecycle.Lifecycle.State);
+    property public androidx.lifecycle.Lifecycle.State currentState;
+    property public int observerCount;
+    field public static final androidx.lifecycle.LifecycleRegistry.Companion Companion;
+  }
+
+  public static final class LifecycleRegistry.Companion {
+    method @VisibleForTesting public androidx.lifecycle.LifecycleRegistry createUnsafe(androidx.lifecycle.LifecycleOwner owner);
   }
 
   @Deprecated public interface LifecycleRegistryOwner extends androidx.lifecycle.LifecycleOwner {
diff --git a/lifecycle/lifecycle-runtime/api/restricted_current.txt b/lifecycle/lifecycle-runtime/api/restricted_current.txt
index 74af488..4602a21 100644
--- a/lifecycle/lifecycle-runtime/api/restricted_current.txt
+++ b/lifecycle/lifecycle-runtime/api/restricted_current.txt
@@ -2,15 +2,22 @@
 package androidx.lifecycle {
 
   public class LifecycleRegistry extends androidx.lifecycle.Lifecycle {
-    ctor public LifecycleRegistry(androidx.lifecycle.LifecycleOwner);
-    method public void addObserver(androidx.lifecycle.LifecycleObserver);
-    method @VisibleForTesting public static androidx.lifecycle.LifecycleRegistry createUnsafe(androidx.lifecycle.LifecycleOwner);
+    ctor public LifecycleRegistry(androidx.lifecycle.LifecycleOwner provider);
+    method public void addObserver(androidx.lifecycle.LifecycleObserver observer);
+    method @VisibleForTesting public static final androidx.lifecycle.LifecycleRegistry createUnsafe(androidx.lifecycle.LifecycleOwner owner);
     method public androidx.lifecycle.Lifecycle.State getCurrentState();
     method public int getObserverCount();
-    method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event);
-    method @Deprecated @MainThread public void markState(androidx.lifecycle.Lifecycle.State);
-    method public void removeObserver(androidx.lifecycle.LifecycleObserver);
-    method @MainThread public void setCurrentState(androidx.lifecycle.Lifecycle.State);
+    method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event event);
+    method @Deprecated @MainThread public void markState(androidx.lifecycle.Lifecycle.State state);
+    method public void removeObserver(androidx.lifecycle.LifecycleObserver observer);
+    method public void setCurrentState(androidx.lifecycle.Lifecycle.State);
+    property public androidx.lifecycle.Lifecycle.State currentState;
+    property public int observerCount;
+    field public static final androidx.lifecycle.LifecycleRegistry.Companion Companion;
+  }
+
+  public static final class LifecycleRegistry.Companion {
+    method @VisibleForTesting public androidx.lifecycle.LifecycleRegistry createUnsafe(androidx.lifecycle.LifecycleOwner owner);
   }
 
   @Deprecated public interface LifecycleRegistryOwner extends androidx.lifecycle.LifecycleOwner {
diff --git a/lifecycle/lifecycle-runtime/build.gradle b/lifecycle/lifecycle-runtime/build.gradle
index bd48528..d47da39 100644
--- a/lifecycle/lifecycle-runtime/build.gradle
+++ b/lifecycle/lifecycle-runtime/build.gradle
@@ -3,6 +3,7 @@
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
+    id("org.jetbrains.kotlin.android")
 }
 
 android {
@@ -13,12 +14,13 @@
 }
 
 dependencies {
+    api(libs.kotlinStdlib)
     api(project(":lifecycle:lifecycle-common"))
 
-    api("androidx.arch.core:core-common:2.1.0")
+    api(project(":arch:core:core-common"))
     // necessary for IJ to resolve dependencies.
     api("androidx.annotation:annotation:1.1.0")
-    implementation("androidx.arch.core:core-runtime:2.1.0")
+    implementation(project(":arch:core:core-runtime"))
 
     testImplementation(libs.junit)
     testImplementation(libs.mockitoCore4)
diff --git a/lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java b/lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java
deleted file mode 100644
index 5279d4c..0000000
--- a/lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle;
-
-import static androidx.lifecycle.Lifecycle.State.DESTROYED;
-import static androidx.lifecycle.Lifecycle.State.INITIALIZED;
-
-import android.annotation.SuppressLint;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.arch.core.executor.ArchTaskExecutor;
-import androidx.arch.core.internal.FastSafeIterableMap;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * An implementation of {@link Lifecycle} that can handle multiple observers.
- * <p>
- * It is used by Fragments and Support Library Activities. You can also directly use it if you have
- * a custom LifecycleOwner.
- */
-public class LifecycleRegistry extends Lifecycle {
-
-    /**
-     * Custom list that keeps observers and can handle removals / additions during traversal.
-     *
-     * Invariant: at any moment of time for observer1 & observer2:
-     * if addition_order(observer1) < addition_order(observer2), then
-     * state(observer1) >= state(observer2),
-     */
-    private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap =
-            new FastSafeIterableMap<>();
-    /**
-     * Current state
-     */
-    private State mState;
-    /**
-     * The provider that owns this Lifecycle.
-     * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
-     * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
-     * because it keeps strong references on all other listeners, so you'll leak all of them as
-     * well.
-     */
-    private final WeakReference<LifecycleOwner> mLifecycleOwner;
-
-    private int mAddingObserverCounter = 0;
-
-    private boolean mHandlingEvent = false;
-    private boolean mNewEventOccurred = false;
-
-    // we have to keep it for cases:
-    // void onStart() {
-    //     mRegistry.removeObserver(this);
-    //     mRegistry.add(newObserver);
-    // }
-    // newObserver should be brought only to CREATED state during the execution of
-    // this onStart method. our invariant with mObserverMap doesn't help, because parent observer
-    // is no longer in the map.
-    private ArrayList<State> mParentStates = new ArrayList<>();
-    private final boolean mEnforceMainThread;
-
-    /**
-     * Creates a new LifecycleRegistry for the given provider.
-     * <p>
-     * You should usually create this inside your LifecycleOwner class's constructor and hold
-     * onto the same instance.
-     *
-     * @param provider The owner LifecycleOwner
-     */
-    public LifecycleRegistry(@NonNull LifecycleOwner provider) {
-        this(provider, true);
-    }
-
-    private LifecycleRegistry(@NonNull LifecycleOwner provider, boolean enforceMainThread) {
-        mLifecycleOwner = new WeakReference<>(provider);
-        mState = INITIALIZED;
-        mEnforceMainThread = enforceMainThread;
-    }
-
-    /**
-     * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
-     *
-     * @param state new state
-     * @deprecated Use {@link #setCurrentState(State)}.
-     */
-    @Deprecated
-    @MainThread
-    public void markState(@NonNull State state) {
-        enforceMainThreadIfNeeded("markState");
-        setCurrentState(state);
-    }
-
-    /**
-     * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
-     *
-     * @param state new state
-     */
-    @MainThread
-    public void setCurrentState(@NonNull State state) {
-        enforceMainThreadIfNeeded("setCurrentState");
-        moveToState(state);
-    }
-
-    /**
-     * Sets the current state and notifies the observers.
-     * <p>
-     * Note that if the {@code currentState} is the same state as the last call to this method,
-     * calling this method has no effect.
-     *
-     * @param event The event that was received
-     */
-    public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
-        enforceMainThreadIfNeeded("handleLifecycleEvent");
-        moveToState(event.getTargetState());
-    }
-
-    private void moveToState(State next) {
-        if (mState == next) {
-            return;
-        }
-        if (mState == INITIALIZED && next == DESTROYED) {
-            throw new IllegalStateException(
-                    "no event down from " + mState + " in component " + mLifecycleOwner.get());
-        }
-        mState = next;
-        if (mHandlingEvent || mAddingObserverCounter != 0) {
-            mNewEventOccurred = true;
-            // we will figure out what to do on upper level.
-            return;
-        }
-        mHandlingEvent = true;
-        sync();
-        mHandlingEvent = false;
-        if (mState == DESTROYED) {
-            mObserverMap = new FastSafeIterableMap<>();
-        }
-    }
-
-    private boolean isSynced() {
-        if (mObserverMap.size() == 0) {
-            return true;
-        }
-        State eldestObserverState = mObserverMap.eldest().getValue().mState;
-        State newestObserverState = mObserverMap.newest().getValue().mState;
-        return eldestObserverState == newestObserverState && mState == newestObserverState;
-    }
-
-    private State calculateTargetState(LifecycleObserver observer) {
-        Map.Entry<LifecycleObserver, ObserverWithState> previous = mObserverMap.ceil(observer);
-
-        State siblingState = previous != null ? previous.getValue().mState : null;
-        State parentState = !mParentStates.isEmpty() ? mParentStates.get(mParentStates.size() - 1)
-                : null;
-        return min(min(mState, siblingState), parentState);
-    }
-
-    @Override
-    public void addObserver(@NonNull LifecycleObserver observer) {
-        enforceMainThreadIfNeeded("addObserver");
-        State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
-        ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
-        ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
-
-        if (previous != null) {
-            return;
-        }
-        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
-        if (lifecycleOwner == null) {
-            // it is null we should be destroyed. Fallback quickly
-            return;
-        }
-
-        boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
-        State targetState = calculateTargetState(observer);
-        mAddingObserverCounter++;
-        while ((statefulObserver.mState.compareTo(targetState) < 0
-                && mObserverMap.contains(observer))) {
-            pushParentState(statefulObserver.mState);
-            final Event event = Event.upFrom(statefulObserver.mState);
-            if (event == null) {
-                throw new IllegalStateException("no event up from " + statefulObserver.mState);
-            }
-            statefulObserver.dispatchEvent(lifecycleOwner, event);
-            popParentState();
-            // mState / subling may have been changed recalculate
-            targetState = calculateTargetState(observer);
-        }
-
-        if (!isReentrance) {
-            // we do sync only on the top level.
-            sync();
-        }
-        mAddingObserverCounter--;
-    }
-
-    private void popParentState() {
-        mParentStates.remove(mParentStates.size() - 1);
-    }
-
-    private void pushParentState(State state) {
-        mParentStates.add(state);
-    }
-
-    @Override
-    public void removeObserver(@NonNull LifecycleObserver observer) {
-        enforceMainThreadIfNeeded("removeObserver");
-        // we consciously decided not to send destruction events here in opposition to addObserver.
-        // Our reasons for that:
-        // 1. These events haven't yet happened at all. In contrast to events in addObservers, that
-        // actually occurred but earlier.
-        // 2. There are cases when removeObserver happens as a consequence of some kind of fatal
-        // event. If removeObserver method sends destruction events, then a clean up routine becomes
-        // more cumbersome. More specific example of that is: your LifecycleObserver listens for
-        // a web connection, in the usual routine in OnStop method you report to a server that a
-        // session has just ended and you close the connection. Now let's assume now that you
-        // lost an internet and as a result you removed this observer. If you get destruction
-        // events in removeObserver, you should have a special case in your onStop method that
-        // checks if your web connection died and you shouldn't try to report anything to a server.
-        mObserverMap.remove(observer);
-    }
-
-    /**
-     * The number of observers.
-     *
-     * @return The number of observers.
-     */
-    @SuppressWarnings("WeakerAccess")
-    public int getObserverCount() {
-        enforceMainThreadIfNeeded("getObserverCount");
-        return mObserverMap.size();
-    }
-
-    @NonNull
-    @Override
-    public State getCurrentState() {
-        return mState;
-    }
-
-    private void forwardPass(LifecycleOwner lifecycleOwner) {
-        Iterator<Map.Entry<LifecycleObserver, ObserverWithState>> ascendingIterator =
-                mObserverMap.iteratorWithAdditions();
-        while (ascendingIterator.hasNext() && !mNewEventOccurred) {
-            Map.Entry<LifecycleObserver, ObserverWithState> entry = ascendingIterator.next();
-            ObserverWithState observer = entry.getValue();
-            while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
-                    && mObserverMap.contains(entry.getKey()))) {
-                pushParentState(observer.mState);
-                final Event event = Event.upFrom(observer.mState);
-                if (event == null) {
-                    throw new IllegalStateException("no event up from " + observer.mState);
-                }
-                observer.dispatchEvent(lifecycleOwner, event);
-                popParentState();
-            }
-        }
-    }
-
-    private void backwardPass(LifecycleOwner lifecycleOwner) {
-        Iterator<Map.Entry<LifecycleObserver, ObserverWithState>> descendingIterator =
-                mObserverMap.descendingIterator();
-        while (descendingIterator.hasNext() && !mNewEventOccurred) {
-            Map.Entry<LifecycleObserver, ObserverWithState> entry = descendingIterator.next();
-            ObserverWithState observer = entry.getValue();
-            while ((observer.mState.compareTo(mState) > 0 && !mNewEventOccurred
-                    && mObserverMap.contains(entry.getKey()))) {
-                Event event = Event.downFrom(observer.mState);
-                if (event == null) {
-                    throw new IllegalStateException("no event down from " + observer.mState);
-                }
-                pushParentState(event.getTargetState());
-                observer.dispatchEvent(lifecycleOwner, event);
-                popParentState();
-            }
-        }
-    }
-
-    // happens only on the top of stack (never in reentrance),
-    // so it doesn't have to take in account parents
-    private void sync() {
-        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
-        if (lifecycleOwner == null) {
-            throw new IllegalStateException("LifecycleOwner of this LifecycleRegistry is already"
-                    + "garbage collected. It is too late to change lifecycle state.");
-        }
-        while (!isSynced()) {
-            mNewEventOccurred = false;
-            // no need to check eldest for nullability, because isSynced does it for us.
-            if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
-                backwardPass(lifecycleOwner);
-            }
-            Map.Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
-            if (!mNewEventOccurred && newest != null
-                    && mState.compareTo(newest.getValue().mState) > 0) {
-                forwardPass(lifecycleOwner);
-            }
-        }
-        mNewEventOccurred = false;
-    }
-
-    @SuppressLint("RestrictedApi")
-    private void enforceMainThreadIfNeeded(String methodName) {
-        if (mEnforceMainThread) {
-            if (!ArchTaskExecutor.getInstance().isMainThread()) {
-                throw new IllegalStateException("Method " + methodName + " must be called on the "
-                        + "main thread");
-            }
-        }
-    }
-
-    /**
-     * Creates a new LifecycleRegistry for the given provider, that doesn't check
-     * that its methods are called on the threads other than main.
-     * <p>
-     * LifecycleRegistry is not synchronized: if multiple threads access this {@code
-     * LifecycleRegistry}, it must be synchronized externally.
-     * <p>
-     * Another possible use-case for this method is JVM testing, when main thread is not present.
-     */
-    @VisibleForTesting
-    @NonNull
-    public static LifecycleRegistry createUnsafe(@NonNull LifecycleOwner owner) {
-        return new LifecycleRegistry(owner, false);
-    }
-
-    static State min(@NonNull State state1, @Nullable State state2) {
-        return state2 != null && state2.compareTo(state1) < 0 ? state2 : state1;
-    }
-
-    static class ObserverWithState {
-        State mState;
-        LifecycleEventObserver mLifecycleObserver;
-
-        ObserverWithState(LifecycleObserver observer, State initialState) {
-            mLifecycleObserver = Lifecycling.lifecycleEventObserver(observer);
-            mState = initialState;
-        }
-
-        void dispatchEvent(LifecycleOwner owner, Event event) {
-            State newState = event.getTargetState();
-            mState = min(mState, newState);
-            mLifecycleObserver.onStateChanged(owner, event);
-            mState = newState;
-        }
-    }
-}
diff --git a/lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.kt b/lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.kt
new file mode 100644
index 0000000..a0d64db
--- /dev/null
+++ b/lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.kt
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2017 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.lifecycle
+
+import android.annotation.SuppressLint
+import androidx.annotation.MainThread
+import androidx.annotation.VisibleForTesting
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.internal.FastSafeIterableMap
+import java.lang.ref.WeakReference
+
+/**
+ * An implementation of [Lifecycle] that can handle multiple observers.
+ *
+ * It is used by Fragments and Support Library Activities. You can also directly use it if you have
+ * a custom LifecycleOwner.
+ */
+open class LifecycleRegistry private constructor(
+    provider: LifecycleOwner,
+    private val enforceMainThread: Boolean
+) : Lifecycle() {
+    /**
+     * Custom list that keeps observers and can handle removals / additions during traversal.
+     *
+     * Invariant: at any moment of time for observer1 & observer2:
+     * if addition_order(observer1) < addition_order(observer2), then
+     * state(observer1) >= state(observer2),
+     */
+    private var observerMap = FastSafeIterableMap<LifecycleObserver, ObserverWithState>()
+
+    /**
+     * Current state
+     */
+    private var state: State = State.INITIALIZED
+
+    /**
+     * The provider that owns this Lifecycle.
+     * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
+     * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
+     * because it keeps strong references on all other listeners, so you'll leak all of them as
+     * well.
+     */
+    private val lifecycleOwner: WeakReference<LifecycleOwner>
+    private var addingObserverCounter = 0
+    private var handlingEvent = false
+    private var newEventOccurred = false
+
+    // we have to keep it for cases:
+    // void onStart() {
+    //     mRegistry.removeObserver(this);
+    //     mRegistry.add(newObserver);
+    // }
+    // newObserver should be brought only to CREATED state during the execution of
+    // this onStart method. our invariant with observerMap doesn't help, because parent observer
+    // is no longer in the map.
+    private var parentStates = ArrayList<State>()
+
+    /**
+     * Creates a new LifecycleRegistry for the given provider.
+     *
+     * You should usually create this inside your LifecycleOwner class's constructor and hold
+     * onto the same instance.
+     *
+     * @param provider The owner LifecycleOwner
+     */
+    constructor(provider: LifecycleOwner) : this(provider, true)
+
+    init {
+        lifecycleOwner = WeakReference(provider)
+    }
+
+    /**
+     * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
+     *
+     * @param state new state
+     */
+    @MainThread
+    @Deprecated("Override [currentState].")
+    open fun markState(state: State) {
+        enforceMainThreadIfNeeded("markState")
+        currentState = state
+    }
+
+    override var currentState: State
+        get() = state
+        /**
+         * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
+         *
+         * @param state new state
+         */
+        set(state) {
+            enforceMainThreadIfNeeded("setCurrentState")
+            moveToState(state)
+        }
+
+    /**
+     * Sets the current state and notifies the observers.
+     *
+     * Note that if the `currentState` is the same state as the last call to this method,
+     * calling this method has no effect.
+     *
+     * @param event The event that was received
+     */
+    open fun handleLifecycleEvent(event: Event) {
+        enforceMainThreadIfNeeded("handleLifecycleEvent")
+        moveToState(event.targetState)
+    }
+
+    private fun moveToState(next: State) {
+        if (state == next) {
+            return
+        }
+        check(!(state == State.INITIALIZED && next == State.DESTROYED)) {
+            "no event down from $state in component ${lifecycleOwner.get()}"
+        }
+        state = next
+        if (handlingEvent || addingObserverCounter != 0) {
+            newEventOccurred = true
+            // we will figure out what to do on upper level.
+            return
+        }
+        handlingEvent = true
+        sync()
+        handlingEvent = false
+        if (state == State.DESTROYED) {
+            observerMap = FastSafeIterableMap()
+        }
+    }
+
+    private val isSynced: Boolean
+        get() {
+            if (observerMap.size() == 0) {
+                return true
+            }
+            val eldestObserverState = observerMap.eldest()!!.value.state
+            val newestObserverState = observerMap.newest()!!.value.state
+            return eldestObserverState == newestObserverState && state == newestObserverState
+        }
+
+    private fun calculateTargetState(observer: LifecycleObserver): State {
+        val map = observerMap.ceil(observer)
+        val siblingState = map?.value?.state
+        val parentState =
+            if (parentStates.isNotEmpty()) parentStates[parentStates.size - 1] else null
+        return min(min(state, siblingState), parentState)
+    }
+
+    /**
+     * Adds a LifecycleObserver that will be notified when the LifecycleOwner changes
+     * state.
+     *
+     * The given observer will be brought to the current state of the LifecycleOwner.
+     * For example, if the LifecycleOwner is in [Lifecycle.State.STARTED] state, the given observer
+     * will receive [Lifecycle.Event.ON_CREATE], [Lifecycle.Event.ON_START] events.
+     *
+     * @param observer The observer to notify.
+     *
+     * @throws IllegalStateException if no event up from observer's initial state
+     */
+    override fun addObserver(observer: LifecycleObserver) {
+        enforceMainThreadIfNeeded("addObserver")
+        val initialState = if (state == State.DESTROYED) State.DESTROYED else State.INITIALIZED
+        val statefulObserver = ObserverWithState(observer, initialState)
+        val previous = observerMap.putIfAbsent(observer, statefulObserver)
+        if (previous != null) {
+            return
+        }
+        val lifecycleOwner = lifecycleOwner.get()
+            ?: // it is null we should be destroyed. Fallback quickly
+            return
+        val isReentrance = addingObserverCounter != 0 || handlingEvent
+        var targetState = calculateTargetState(observer)
+        addingObserverCounter++
+        while (statefulObserver.state < targetState && observerMap.contains(observer)
+        ) {
+            pushParentState(statefulObserver.state)
+            val event = Event.upFrom(statefulObserver.state)
+                ?: throw IllegalStateException("no event up from ${statefulObserver.state}")
+            statefulObserver.dispatchEvent(lifecycleOwner, event)
+            popParentState()
+            // mState / subling may have been changed recalculate
+            targetState = calculateTargetState(observer)
+        }
+        if (!isReentrance) {
+            // we do sync only on the top level.
+            sync()
+        }
+        addingObserverCounter--
+    }
+
+    private fun popParentState() {
+        parentStates.removeAt(parentStates.size - 1)
+    }
+
+    private fun pushParentState(state: State) {
+        parentStates.add(state)
+    }
+
+    override fun removeObserver(observer: LifecycleObserver) {
+        enforceMainThreadIfNeeded("removeObserver")
+        // we consciously decided not to send destruction events here in opposition to addObserver.
+        // Our reasons for that:
+        // 1. These events haven't yet happened at all. In contrast to events in addObservers, that
+        // actually occurred but earlier.
+        // 2. There are cases when removeObserver happens as a consequence of some kind of fatal
+        // event. If removeObserver method sends destruction events, then a clean up routine becomes
+        // more cumbersome. More specific example of that is: your LifecycleObserver listens for
+        // a web connection, in the usual routine in OnStop method you report to a server that a
+        // session has just ended and you close the connection. Now let's assume now that you
+        // lost an internet and as a result you removed this observer. If you get destruction
+        // events in removeObserver, you should have a special case in your onStop method that
+        // checks if your web connection died and you shouldn't try to report anything to a server.
+        observerMap.remove(observer)
+    }
+
+    /**
+     * The number of observers.
+     *
+     * @return The number of observers.
+     */
+    open val observerCount: Int
+        get() {
+            enforceMainThreadIfNeeded("getObserverCount")
+            return observerMap.size()
+        }
+
+    private fun forwardPass(lifecycleOwner: LifecycleOwner) {
+        @Suppress()
+        val ascendingIterator: Iterator<Map.Entry<LifecycleObserver, ObserverWithState>> =
+            observerMap.iteratorWithAdditions()
+        while (ascendingIterator.hasNext() && !newEventOccurred) {
+            val (key, observer) = ascendingIterator.next()
+            while (observer.state < state && !newEventOccurred && observerMap.contains(key)
+            ) {
+                pushParentState(observer.state)
+                val event = Event.upFrom(observer.state)
+                    ?: throw IllegalStateException("no event up from ${observer.state}")
+                observer.dispatchEvent(lifecycleOwner, event)
+                popParentState()
+            }
+        }
+    }
+
+    private fun backwardPass(lifecycleOwner: LifecycleOwner) {
+        val descendingIterator = observerMap.descendingIterator()
+        while (descendingIterator.hasNext() && !newEventOccurred) {
+            val (key, observer) = descendingIterator.next()
+            while (observer.state > state && !newEventOccurred && observerMap.contains(key)
+            ) {
+                val event = Event.downFrom(observer.state)
+                    ?: throw IllegalStateException("no event down from ${observer.state}")
+                pushParentState(event.targetState)
+                observer.dispatchEvent(lifecycleOwner, event)
+                popParentState()
+            }
+        }
+    }
+
+    // happens only on the top of stack (never in reentrance),
+    // so it doesn't have to take in account parents
+    private fun sync() {
+        val lifecycleOwner = lifecycleOwner.get()
+            ?: throw IllegalStateException(
+                "LifecycleOwner of this LifecycleRegistry is already " +
+                    "garbage collected. It is too late to change lifecycle state."
+            )
+        while (!isSynced) {
+            newEventOccurred = false
+            if (state < observerMap.eldest()!!.value.state) {
+                backwardPass(lifecycleOwner)
+            }
+            val newest = observerMap.newest()
+            if (!newEventOccurred && newest != null && state > newest.value.state) {
+                forwardPass(lifecycleOwner)
+            }
+        }
+        newEventOccurred = false
+    }
+
+    @SuppressLint("RestrictedApi")
+    private fun enforceMainThreadIfNeeded(methodName: String) {
+        if (enforceMainThread) {
+            check(ArchTaskExecutor.getInstance().isMainThread) {
+                ("Method $methodName must be called on the main thread")
+            }
+        }
+    }
+
+    internal class ObserverWithState(observer: LifecycleObserver?, initialState: State) {
+        var state: State
+        var lifecycleObserver: LifecycleEventObserver
+
+        init {
+            lifecycleObserver = Lifecycling.lifecycleEventObserver(observer)
+            state = initialState
+        }
+
+        fun dispatchEvent(owner: LifecycleOwner?, event: Event) {
+            val newState = event.targetState
+            state = min(state, newState)
+            lifecycleObserver.onStateChanged(owner!!, event)
+            state = newState
+        }
+    }
+
+    companion object {
+        /**
+         * Creates a new LifecycleRegistry for the given provider, that doesn't check
+         * that its methods are called on the threads other than main.
+         *
+         * LifecycleRegistry is not synchronized: if multiple threads access this `LifecycleRegistry`, it must be synchronized externally.
+         *
+         * Another possible use-case for this method is JVM testing, when main thread is not present.
+         */
+        @JvmStatic
+        @VisibleForTesting
+        fun createUnsafe(owner: LifecycleOwner): LifecycleRegistry {
+            return LifecycleRegistry(owner, false)
+        }
+
+        @JvmStatic
+        internal fun min(state1: State, state2: State?): State {
+            return if ((state2 != null) && (state2 < state1)) state2 else state1
+        }
+    }
+}
\ No newline at end of file
diff --git a/media2/media2-widget/src/main/res/values-en-rCA/strings.xml b/media2/media2-widget/src/main/res/values-en-rCA/strings.xml
index a15ebb7..bf17433 100644
--- a/media2/media2-widget/src/main/res/values-en-rCA/strings.xml
+++ b/media2/media2-widget/src/main/res/values-en-rCA/strings.xml
@@ -24,12 +24,12 @@
     <string name="MediaControlView_playback_speed_normal" msgid="2029510260288453183">"Normal"</string>
     <string name="MediaControlView_time_placeholder" msgid="6734584158942500617">"00:00:00"</string>
     <string name="MediaControlView_subtitle_track_number_text" msgid="2241078077382492349">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g>"</string>
-    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> – <xliff:g id="LANG">%2$s</xliff:g>"</string>
+    <string name="MediaControlView_subtitle_track_number_and_lang_text" msgid="2268860463481696609">"Track <xliff:g id="TRACK_NUMBER">%1$d</xliff:g> - <xliff:g id="LANG">%2$s</xliff:g>"</string>
     <string name="MediaControlView_audio_track_number_text" msgid="4263103361854223806">"Track <xliff:g id="AUDIO_NUMBER">%1$d</xliff:g>"</string>
     <string name="mcv2_non_music_title_unknown_text" msgid="2032814146738922144">"Video title unknown"</string>
     <string name="mcv2_music_title_unknown_text" msgid="6037645626002038645">"Song title unknown"</string>
     <string name="mcv2_music_artist_unknown_text" msgid="5393558204040775454">"Artist unknown"</string>
-    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Couldn\'t play the item that you requested"</string>
+    <string name="mcv2_playback_error_text" msgid="6061787693725630293">"Couldn\'t play the item you requested"</string>
     <string name="mcv2_error_dialog_button" msgid="5940167897992933850">"OK"</string>
     <string name="mcv2_back_button_desc" msgid="1540894858499118373">"Back"</string>
     <string name="mcv2_overflow_left_button_desc" msgid="2749567167276435888">"Back to previous button list"</string>
@@ -44,6 +44,6 @@
     <string name="mcv2_previous_button_desc" msgid="3080315055670437389">"Previous media"</string>
     <string name="mcv2_next_button_desc" msgid="1204572886248099893">"Next media"</string>
     <string name="mcv2_rewind_button_desc" msgid="578809901971186362">"Rewind by 10 seconds"</string>
-    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Go forwards 30 seconds"</string>
+    <string name="mcv2_ffwd_button_desc" msgid="611689280746097673">"Go forward by 30 seconds"</string>
     <string name="mcv2_full_screen_button_desc" msgid="1609817079594941003">"Full screen"</string>
 </resources>
diff --git a/navigation/navigation-dynamic-features-fragment/src/main/res/values-en-rCA/strings.xml b/navigation/navigation-dynamic-features-fragment/src/main/res/values-en-rCA/strings.xml
index 2e4af5a..675c2a8 100644
--- a/navigation/navigation-dynamic-features-fragment/src/main/res/values-en-rCA/strings.xml
+++ b/navigation/navigation-dynamic-features-fragment/src/main/res/values-en-rCA/strings.xml
@@ -22,5 +22,5 @@
     <string name="installing_module" msgid="5968445461040787716">"Installing module:"</string>
     <string name="progress" msgid="8366783942222789124">"Progress:"</string>
     <string name="retry" msgid="1065327189183624288">"Retry"</string>
-    <string name="ok" msgid="4702104660890557116">"OK"</string>
+    <string name="ok" msgid="4702104660890557116">"Okay"</string>
 </resources>
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index 33c2b397..1fe2a82 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -25,7 +25,7 @@
 kotlin.code.style=official
 # Disable docs
 androidx.enableDocumentation=false
-androidx.playground.snapshotBuildId=9282206
-androidx.playground.metalavaBuildId=8993580
+androidx.playground.snapshotBuildId=9304612
+androidx.playground.metalavaBuildId=9295138
 androidx.playground.dokkaBuildId=7472101
 androidx.studio.type=playground
diff --git a/preference/preference/res/values-en-rCA/strings.xml b/preference/preference/res/values-en-rCA/strings.xml
index a16f7bf..7632aea 100644
--- a/preference/preference/res/values-en-rCA/strings.xml
+++ b/preference/preference/res/values-en-rCA/strings.xml
@@ -6,6 +6,6 @@
     <string name="expand_button_title" msgid="2427401033573778270">"Advanced"</string>
     <string name="summary_collapsed_preference_list" msgid="9167775378838880170">"<xliff:g id="CURRENT_ITEMS">%1$s</xliff:g>, <xliff:g id="ADDED_ITEMS">%2$s</xliff:g>"</string>
     <string name="copy" msgid="6083905920877235314">"Copy"</string>
-    <string name="preference_copied" msgid="6685851473431805375">"\'<xliff:g id="SUMMARY">%1$s</xliff:g>\' copied to clipboard."</string>
+    <string name="preference_copied" msgid="6685851473431805375">"\"<xliff:g id="SUMMARY">%1$s</xliff:g>\" copied to clipboard."</string>
     <string name="not_set" msgid="6573031135582639649">"Not set"</string>
 </resources>
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
index f278f79..bc189a4 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
@@ -21,6 +21,7 @@
 import androidx.privacysandbox.tools.core.generator.AidlGenerator
 import androidx.privacysandbox.tools.core.generator.ClientProxyTypeGenerator
 import androidx.privacysandbox.tools.core.generator.ServerBinderCodeConverter
+import androidx.privacysandbox.tools.core.generator.ServiceFactoryFileGenerator
 import androidx.privacysandbox.tools.core.generator.StubDelegatesGenerator
 import androidx.privacysandbox.tools.core.generator.ThrowableParcelConverterFileGenerator
 import androidx.privacysandbox.tools.core.generator.TransportCancellationGenerator
@@ -63,6 +64,7 @@
         generateCallbackProxies()
         generateToolMetadata()
         generateSuspendFunctionUtilities()
+        generateServiceFactoryFile()
     }
 
     private fun generateAidlSources() {
@@ -118,6 +120,16 @@
         ).use { Metadata.toolMetadata.writeTo(it) }
     }
 
+    private fun generateServiceFactoryFile() {
+        // The service factory stubs are generated so that the API Packager can include them in the
+        // API descriptors, and the client can use those symbols without running the API Generator.
+        // It's not intended to be used by the SDK code.
+        val serviceFactoryFileGenerator = ServiceFactoryFileGenerator(generateStubs = true)
+        api.services.forEach {
+            serviceFactoryFileGenerator.generate(it).also(::write)
+        }
+    }
+
     private fun generateSuspendFunctionUtilities() {
         if (!api.hasSuspendFunctions()) return
         TransportCancellationGenerator(basePackageName()).generate().also(::write)
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkFactory.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkFactory.kt
new file mode 100644
index 0000000..d130eaf
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/MySdkFactory.kt
@@ -0,0 +1,10 @@
+package com.mysdk
+
+import android.os.IBinder
+import androidx.privacysandbox.tools.core.GeneratedPublicApi
+
+@GeneratedPublicApi
+public object MySdkFactory {
+    @Suppress("UNUSED_PARAMETER")
+    public fun wrapToMySdk(binder: IBinder): MySdk = throw RuntimeException("Stub!")
+}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/BackwardsCompatibleSdkFactory.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/BackwardsCompatibleSdkFactory.kt
new file mode 100644
index 0000000..8fa3062
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/BackwardsCompatibleSdkFactory.kt
@@ -0,0 +1,11 @@
+package com.mysdk
+
+import android.os.IBinder
+import androidx.privacysandbox.tools.core.GeneratedPublicApi
+
+@GeneratedPublicApi
+public object BackwardsCompatibleSdkFactory {
+    @Suppress("UNUSED_PARAMETER")
+    public fun wrapToBackwardsCompatibleSdk(binder: IBinder): BackwardsCompatibleSdk = throw
+            RuntimeException("Stub!")
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
index 56e1a34..d18a08a 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
@@ -17,12 +17,14 @@
 package androidx.privacysandbox.tools.apigenerator
 
 import androidx.privacysandbox.tools.apigenerator.parser.ApiStubParser
+import androidx.privacysandbox.tools.core.Metadata
 import androidx.privacysandbox.tools.core.generator.AidlCompiler
 import androidx.privacysandbox.tools.core.generator.AidlGenerator
 import androidx.privacysandbox.tools.core.generator.BinderCodeConverter
 import androidx.privacysandbox.tools.core.generator.ClientBinderCodeConverter
 import androidx.privacysandbox.tools.core.generator.ClientProxyTypeGenerator
 import androidx.privacysandbox.tools.core.generator.PrivacySandboxExceptionFileGenerator
+import androidx.privacysandbox.tools.core.generator.ServiceFactoryFileGenerator
 import androidx.privacysandbox.tools.core.generator.StubDelegatesGenerator
 import androidx.privacysandbox.tools.core.generator.ThrowableParcelConverterFileGenerator
 import androidx.privacysandbox.tools.core.generator.ValueConverterFileGenerator
@@ -30,6 +32,8 @@
 import androidx.privacysandbox.tools.core.model.ParsedApi
 import androidx.privacysandbox.tools.core.model.getOnlyService
 import androidx.privacysandbox.tools.core.model.hasSuspendFunctions
+import androidx.privacysandbox.tools.core.proto.PrivacySandboxToolsProtocol.ToolMetadata
+import com.google.protobuf.InvalidProtocolBufferException
 import java.io.File
 import java.nio.file.Path
 import java.util.zip.ZipInputStream
@@ -40,6 +44,7 @@
 import kotlin.io.path.isDirectory
 import kotlin.io.path.moveTo
 import kotlin.io.path.outputStream
+import kotlin.io.path.readBytes
 
 /** Generate source files for communicating with an SDK running in the Privacy Sandbox. */
 class PrivacySandboxApiGenerator {
@@ -173,12 +178,32 @@
                     }
             }
 
+            ensureValidMetadata(workingDirectory.resolve(Metadata.filePath))
             return ApiStubParser.parse(workingDirectory)
         } finally {
             workingDirectory.toFile().deleteRecursively()
         }
     }
 
+    private fun ensureValidMetadata(metadataFile: Path) {
+        require(metadataFile.exists()) {
+            "Missing tool metadata in SDK API descriptor."
+        }
+
+        val metadata = try {
+            ToolMetadata.parseFrom(metadataFile.readBytes())
+        } catch (e: InvalidProtocolBufferException) {
+            throw IllegalArgumentException("Invalid Privacy Sandbox tool metadata.", e)
+        }
+
+        val sdkCodeGenerationVersion = metadata.codeGenerationVersion
+        val consumerVersion = Metadata.toolMetadata.codeGenerationVersion
+        require(sdkCodeGenerationVersion <= consumerVersion) {
+            "SDK uses incompatible Privacy Sandbox tooling " +
+                "(version $sdkCodeGenerationVersion). Current version is $consumerVersion."
+        }
+    }
+
     private fun generateSuspendFunctionUtilities(
         api: ParsedApi,
         basePackageName: String,
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceFactoryFileGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceFactoryFileGenerator.kt
deleted file mode 100644
index 293d0ad..0000000
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceFactoryFileGenerator.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.privacysandbox.tools.apigenerator
-
-import androidx.privacysandbox.tools.core.generator.SpecNames.iBinderClassName
-import androidx.privacysandbox.tools.core.generator.addCommonSettings
-import androidx.privacysandbox.tools.core.generator.aidlInterfaceNameSpec
-import androidx.privacysandbox.tools.core.generator.build
-import androidx.privacysandbox.tools.core.generator.clientProxyNameSpec
-import androidx.privacysandbox.tools.core.model.AnnotatedInterface
-import com.squareup.kotlinpoet.ClassName
-import com.squareup.kotlinpoet.FileSpec
-import com.squareup.kotlinpoet.FunSpec
-import com.squareup.kotlinpoet.ParameterSpec
-
-internal class ServiceFactoryFileGenerator {
-
-    fun generate(service: AnnotatedInterface): FileSpec =
-        FileSpec.builder(service.type.packageName, "${service.type.simpleName}Factory").build {
-            addCommonSettings()
-            addFunction(generateFactoryFunction(service))
-        }
-
-    private fun generateFactoryFunction(service: AnnotatedInterface) =
-        FunSpec.builder("wrapTo${service.type.simpleName}").build {
-            addParameter(ParameterSpec("binder", iBinderClassName))
-            returns(ClassName(service.type.packageName, service.type.simpleName))
-            addStatement(
-                "return %T(%T.Stub.asInterface(binder))",
-                service.clientProxyNameSpec(), service.aidlInterfaceNameSpec()
-            )
-        }
-}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/VersionCompatibilityCheckTest.kt b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/VersionCompatibilityCheckTest.kt
index 33cf59b0..10561b1 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/VersionCompatibilityCheckTest.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/VersionCompatibilityCheckTest.kt
@@ -23,7 +23,6 @@
 import java.nio.file.Files
 import java.nio.file.Path
 import kotlin.io.path.Path
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -46,7 +45,6 @@
     private val validMetadataContent = Metadata.toolMetadata.toByteArray()
 
     @Test
-    @Ignore("b/255740194")
     fun sdkDescriptorWithMissingMetadata_throws() {
         assertThrows<IllegalArgumentException> {
             runGeneratorWithResources(mapOf())
@@ -54,7 +52,6 @@
     }
 
     @Test
-    @Ignore("b/255740194")
     fun sdkDescriptorWithMetadataInWrongPath_throws() {
         assertThrows<IllegalArgumentException> {
             runGeneratorWithResources(
@@ -64,7 +61,6 @@
     }
 
     @Test
-    @Ignore("b/255740194")
     fun sdkDescriptorWithInvalidMetadataContent_throws() {
         assertThrows<IllegalArgumentException> {
             runGeneratorWithResources(
@@ -74,7 +70,6 @@
     }
 
     @Test
-    @Ignore("b/255740194")
     fun sdkDescriptorWithIncompatibleVersion_throws() {
         val sdkMetadata = ToolMetadata.newBuilder()
             .setCodeGenerationVersion(999)
@@ -89,7 +84,6 @@
     }
 
     @Test
-    @Ignore("b/255740194")
     fun sdkDescriptorWithLowerVersion_isCompatible() {
         val sdkMetadata = ToolMetadata.newBuilder()
             .setCodeGenerationVersion(0)
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceFactory.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceFactory.kt
index 0a284a0..5d3b78b 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceFactory.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceFactory.kt
@@ -2,5 +2,7 @@
 
 import android.os.IBinder
 
-public fun wrapToSdkService(binder: IBinder): SdkService =
-        SdkServiceClientProxy(ISdkService.Stub.asInterface(binder))
+public object SdkServiceFactory {
+    public fun wrapToSdkService(binder: IBinder): SdkService =
+            SdkServiceClientProxy(ISdkService.Stub.asInterface(binder))
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySdkFactory.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySdkFactory.kt
index 014a616..2ba3434 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySdkFactory.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySdkFactory.kt
@@ -2,4 +2,7 @@
 
 import android.os.IBinder
 
-public fun wrapToMySdk(binder: IBinder): MySdk = MySdkClientProxy(IMySdk.Stub.asInterface(binder))
+public object MySdkFactory {
+    public fun wrapToMySdk(binder: IBinder): MySdk =
+            MySdkClientProxy(IMySdk.Stub.asInterface(binder))
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdkFactory.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdkFactory.kt
index db02846..722c50a 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdkFactory.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/primitives/output/com/mysdk/TestSandboxSdkFactory.kt
@@ -2,5 +2,7 @@
 
 import android.os.IBinder
 
-public fun wrapToTestSandboxSdk(binder: IBinder): TestSandboxSdk =
-        TestSandboxSdkClientProxy(ITestSandboxSdk.Stub.asInterface(binder))
+public object TestSandboxSdkFactory {
+    public fun wrapToTestSandboxSdk(binder: IBinder): TestSandboxSdk =
+            TestSandboxSdkClientProxy(ITestSandboxSdk.Stub.asInterface(binder))
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkInterfaceFactory.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkInterfaceFactory.kt
index 6a84537..990f74e 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkInterfaceFactory.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/SdkInterfaceFactory.kt
@@ -2,5 +2,7 @@
 
 import android.os.IBinder
 
-public fun wrapToSdkInterface(binder: IBinder): SdkInterface =
-        SdkInterfaceClientProxy(ISdkInterface.Stub.asInterface(binder))
+public object SdkInterfaceFactory {
+    public fun wrapToSdkInterface(binder: IBinder): SdkInterface =
+            SdkInterfaceClientProxy(ISdkInterface.Stub.asInterface(binder))
+}
diff --git a/privacysandbox/tools/tools-apipackager/src/main/java/androidx/privacysandbox/tools/apipackager/AnnotationInspector.kt b/privacysandbox/tools/tools-apipackager/src/main/java/androidx/privacysandbox/tools/apipackager/AnnotationInspector.kt
index 5d8437f..c7020f3 100644
--- a/privacysandbox/tools/tools-apipackager/src/main/java/androidx/privacysandbox/tools/apipackager/AnnotationInspector.kt
+++ b/privacysandbox/tools/tools-apipackager/src/main/java/androidx/privacysandbox/tools/apipackager/AnnotationInspector.kt
@@ -20,6 +20,7 @@
 import androidx.privacysandbox.tools.PrivacySandboxInterface
 import androidx.privacysandbox.tools.PrivacySandboxService
 import androidx.privacysandbox.tools.PrivacySandboxValue
+import androidx.privacysandbox.tools.core.GeneratedPublicApi
 import java.nio.file.Path
 import kotlin.io.path.readBytes
 import org.objectweb.asm.AnnotationVisitor
@@ -34,6 +35,7 @@
         PrivacySandboxInterface::class,
         PrivacySandboxService::class,
         PrivacySandboxValue::class,
+        GeneratedPublicApi::class,
     )
 
     fun hasPrivacySandboxAnnotation(classFile: Path): Boolean {
diff --git a/privacysandbox/tools/tools-apipackager/src/test/java/androidx/privacysandbox/tools/apipackager/PrivacySandboxApiPackagerTest.kt b/privacysandbox/tools/tools-apipackager/src/test/java/androidx/privacysandbox/tools/apipackager/PrivacySandboxApiPackagerTest.kt
index d29527c..bd3cc87 100644
--- a/privacysandbox/tools/tools-apipackager/src/test/java/androidx/privacysandbox/tools/apipackager/PrivacySandboxApiPackagerTest.kt
+++ b/privacysandbox/tools/tools-apipackager/src/test/java/androidx/privacysandbox/tools/apipackager/PrivacySandboxApiPackagerTest.kt
@@ -90,6 +90,7 @@
                     |import androidx.privacysandbox.tools.PrivacySandboxCallback
                     |import androidx.privacysandbox.tools.PrivacySandboxService
                     |import androidx.privacysandbox.tools.PrivacySandboxValue
+                    |import androidx.privacysandbox.tools.core.GeneratedPublicApi
                     |
                     |@PrivacySandboxService
                     |interface MySdk
@@ -99,6 +100,11 @@
                     |
                     |@PrivacySandboxCallback
                     |interface MySdkCallback
+                    |
+                    |@GeneratedPublicApi
+                    |object MySdkFactory {
+                    |    fun wrapToMySdk(): MySdk = throw RuntimeException("Stub!")
+                    |}
                 """.trimMargin()
             )
         )
@@ -111,9 +117,10 @@
                 |import com.mysdk.MySdk
                 |import com.mysdk.Value
                 |import com.mysdk.MySdkCallback
+                |import com.mysdk.MySdkFactory.wrapToMySdk
                 |
                 |class App(
-                |    val sdk: MySdk,
+                |    val sdk: MySdk = wrapToMySdk(),
                 |    val sdkValue: Value,
                 |    val callback: MySdkCallback,
                 |)
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/GeneratedPublicApi.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/GeneratedPublicApi.kt
new file mode 100644
index 0000000..6a4fef0
--- /dev/null
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/GeneratedPublicApi.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.tools.core
+
+/**
+ * Indicates that a class was generated by the API Compiler and is part of the public facing
+ * SDK API.
+ *
+ * The API Packager will include these classes in the API descriptors.
+ *
+ * This annotation is for internal usage and will only be set by the API Compiler.
+ * The API Compiler will fail if the annotation is present in the source code.
+ */
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.CLASS)
+annotation class GeneratedPublicApi
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ServiceFactoryFileGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ServiceFactoryFileGenerator.kt
new file mode 100644
index 0000000..8a68c35
--- /dev/null
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ServiceFactoryFileGenerator.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.tools.core.generator
+
+import androidx.privacysandbox.tools.core.GeneratedPublicApi
+import androidx.privacysandbox.tools.core.model.AnnotatedInterface
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.TypeSpec
+
+class ServiceFactoryFileGenerator(private val generateStubs: Boolean = false) {
+
+    fun generate(service: AnnotatedInterface): FileSpec =
+        FileSpec.builder(service.type.packageName, factoryName(service)).build {
+            addCommonSettings()
+            addType(generateFactory(service))
+        }
+
+    private fun generateFactory(service: AnnotatedInterface) =
+        TypeSpec.objectBuilder(factoryName(service)).build {
+            if (generateStubs) {
+                addAnnotation(GeneratedPublicApi::class)
+            }
+            addFunction(generateFactoryFunction(service))
+        }
+
+    private fun generateFactoryFunction(service: AnnotatedInterface) =
+        FunSpec.builder("wrapTo${service.type.simpleName}").build {
+            addParameter(ParameterSpec("binder", SpecNames.iBinderClassName))
+            returns(ClassName(service.type.packageName, service.type.simpleName))
+            if (generateStubs) {
+                addAnnotation(
+                    AnnotationSpec.builder(Suppress::class).addMember("%S", "UNUSED_PARAMETER")
+                        .build()
+                )
+                addStatement("throw RuntimeException(%S)", "Stub!")
+            } else {
+                addStatement(
+                    "return %T(%T.Stub.asInterface(binder))",
+                    service.clientProxyNameSpec(), service.aidlInterfaceNameSpec()
+                )
+            }
+        }
+
+    private fun factoryName(service: AnnotatedInterface) = "${service.type.simpleName}Factory"
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt
index 9e34117..584d4df 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/PoetExt.kt
@@ -16,12 +16,6 @@
 
 package androidx.room.compiler.codegen
 
-import androidx.room.compiler.processing.XNullability
-import com.squareup.kotlinpoet.javapoet.JClassName
-import com.squareup.kotlinpoet.javapoet.JTypeName
-import com.squareup.kotlinpoet.javapoet.toKClassName
-import com.squareup.kotlinpoet.javapoet.toKTypeName
-
 typealias JCodeBlock = com.squareup.javapoet.CodeBlock
 typealias JCodeBlockBuilder = com.squareup.javapoet.CodeBlock.Builder
 typealias JAnnotationSpecBuilder = com.squareup.javapoet.AnnotationSpec.Builder
@@ -39,7 +33,3 @@
 internal val N = "\$N"
 internal val S = "\$S"
 internal val W = "\$W"
-
-// TODO(b/247247366): Temporary migration API, delete me plz!
-fun JTypeName.toXTypeName() = XTypeName(this, this.toKTypeName(), XNullability.NONNULL)
-fun JClassName.toXClassName() = XClassName(this, this.toKClassName(), XNullability.NONNULL)
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
index d1416b0..7e7dc7b 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
@@ -16,6 +16,8 @@
 
 package androidx.room.compiler.codegen
 
+import com.squareup.kotlinpoet.asClassName as asKClassName
+import com.squareup.kotlinpoet.asTypeName as asKTypeName
 import androidx.room.compiler.processing.XNullability
 import com.squareup.kotlinpoet.ARRAY
 import com.squareup.kotlinpoet.BOOLEAN_ARRAY
@@ -33,8 +35,6 @@
 import com.squareup.kotlinpoet.MUTABLE_SET
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import com.squareup.kotlinpoet.SHORT_ARRAY
-import com.squareup.kotlinpoet.asClassName
-import com.squareup.kotlinpoet.asTypeName
 import com.squareup.kotlinpoet.javapoet.JClassName
 import com.squareup.kotlinpoet.javapoet.JParameterizedTypeName
 import com.squareup.kotlinpoet.javapoet.JTypeName
@@ -62,6 +62,9 @@
     val isPrimitive: Boolean
         get() = java.isPrimitive
 
+    val isBoxedPrimitive: Boolean
+        get() = java.isBoxedPrimitive
+
     /**
      * Returns the raw [XTypeName] if this is a parametrized type name, or itself if not.
      *
@@ -82,11 +85,19 @@
         // TODO(b/248633751): Handle primitive to boxed when becoming nullable?
         return XTypeName(
             java = java,
-            kotlin = kotlin.copy(nullable = nullable),
+            kotlin = if (kotlin != UNAVAILABLE_KTYPE_NAME) {
+                kotlin.copy(nullable = nullable)
+            } else {
+                UNAVAILABLE_KTYPE_NAME
+            },
             nullability = if (nullable) XNullability.NULLABLE else XNullability.NONNULL
         )
     }
 
+    fun equalsIgnoreNullability(other: XTypeName): Boolean {
+        return this.copy(nullable = false) == other.copy(nullable = false)
+    }
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is XTypeName) return false
@@ -113,6 +124,11 @@
         append("]")
     }
 
+    fun toString(codeLanguage: CodeLanguage) = when (codeLanguage) {
+        CodeLanguage.JAVA -> java.toString()
+        CodeLanguage.KOTLIN -> kotlin.toString()
+    }
+
     companion object {
         /**
          * A convenience [XTypeName] that represents [Unit] in Kotlin and `void` in Java.
@@ -121,6 +137,15 @@
             java = JTypeName.VOID,
             kotlin = com.squareup.kotlinpoet.UNIT
         )
+
+        /**
+         * A convenience [XTypeName] that represents [Any] in Kotlin and [Object] in Java.
+         */
+        val ANY_OBJECT = XTypeName(
+            java = JTypeName.OBJECT,
+            kotlin = com.squareup.kotlinpoet.ANY
+        )
+
         val PRIMITIVE_BOOLEAN = Boolean::class.asPrimitiveTypeName()
         val PRIMITIVE_BYTE = Byte::class.asPrimitiveTypeName()
         val PRIMITIVE_SHORT = Short::class.asPrimitiveTypeName()
@@ -130,6 +155,15 @@
         val PRIMITIVE_FLOAT = Float::class.asPrimitiveTypeName()
         val PRIMITIVE_DOUBLE = Double::class.asPrimitiveTypeName()
 
+        val BOXED_BOOLEAN = Boolean::class.asClassName()
+        val BOXED_BYTE = Byte::class.asClassName()
+        val BOXED_SHORT = Short::class.asClassName()
+        val BOXED_INT = Int::class.asClassName()
+        val BOXED_LONG = Long::class.asClassName()
+        val BOXED_CHAR = Char::class.asClassName()
+        val BOXED_FLOAT = Float::class.asClassName()
+        val BOXED_DOUBLE = Double::class.asClassName()
+
         val ANY_WILDCARD = XTypeName(
             java = JWildcardTypeName.subtypeOf(Object::class.java),
             kotlin = com.squareup.kotlinpoet.STAR
@@ -178,7 +212,14 @@
                     JArrayTypeName.of(componentTypeName.java) to
                         ARRAY.parameterizedBy(componentTypeName.kotlin)
             }
-            return XTypeName(java, kotlin)
+            return XTypeName(
+                java = java,
+                kotlin = if (componentTypeName.kotlin != UNAVAILABLE_KTYPE_NAME) {
+                    kotlin
+                } else {
+                    UNAVAILABLE_KTYPE_NAME
+                }
+            )
         }
 
         /**
@@ -191,7 +232,11 @@
         fun getConsumerSuperName(bound: XTypeName): XTypeName {
             return XTypeName(
                 java = JWildcardTypeName.supertypeOf(bound.java),
-                kotlin = KWildcardTypeName.consumerOf(bound.kotlin)
+                kotlin = if (bound.kotlin != UNAVAILABLE_KTYPE_NAME) {
+                    KWildcardTypeName.consumerOf(bound.kotlin)
+                } else {
+                    UNAVAILABLE_KTYPE_NAME
+                }
             )
         }
 
@@ -205,7 +250,11 @@
         fun getProducerExtendsName(bound: XTypeName): XTypeName {
             return XTypeName(
                 java = JWildcardTypeName.subtypeOf(bound.java),
-                kotlin = KWildcardTypeName.producerOf(bound.kotlin)
+                kotlin = if (bound.kotlin != UNAVAILABLE_KTYPE_NAME) {
+                    KWildcardTypeName.producerOf(bound.kotlin)
+                } else {
+                    UNAVAILABLE_KTYPE_NAME
+                }
             )
         }
     }
@@ -241,14 +290,25 @@
     ): XTypeName {
         return XTypeName(
             java = JParameterizedTypeName.get(java, *typeArguments.map { it.java }.toTypedArray()),
-            kotlin = kotlin.parameterizedBy(typeArguments.map { it.kotlin })
+            kotlin = if (
+                kotlin != UNAVAILABLE_KTYPE_NAME &&
+                typeArguments.none { it.kotlin == UNAVAILABLE_KTYPE_NAME }
+            ) {
+                kotlin.parameterizedBy(typeArguments.map { it.kotlin })
+            } else {
+                UNAVAILABLE_KTYPE_NAME
+            }
         )
     }
 
     override fun copy(nullable: Boolean): XClassName {
         return XClassName(
             java = java,
-            kotlin = kotlin.copy(nullable = nullable) as KClassName,
+            kotlin = if (kotlin != UNAVAILABLE_KTYPE_NAME) {
+                kotlin.copy(nullable = nullable) as KClassName
+            } else {
+                UNAVAILABLE_KTYPE_NAME
+            },
             nullability = if (nullable) XNullability.NULLABLE else XNullability.NONNULL
         )
     }
@@ -294,7 +354,7 @@
     } else {
         JClassName.get(this.java)
     }
-    val kClassName = this.asClassName()
+    val kClassName = this.asKClassName()
     return XClassName(
         java = jClassName,
         kotlin = kClassName,
@@ -323,7 +383,7 @@
 fun KClass<*>.asMutableClassName(): XClassName {
     val java = JClassName.get(this.java)
     val kotlin = when (this) {
-        Iterator::class -> MUTABLE_ITERABLE
+        Iterable::class -> MUTABLE_ITERABLE
         Collection::class -> MUTABLE_COLLECTION
         List::class -> MUTABLE_LIST
         Set::class -> MUTABLE_SET
@@ -358,7 +418,7 @@
         "$this does not represent a primitive."
     }
     val jTypeName = getPrimitiveJTypeName(this.java)
-    val kTypeName = this.asTypeName()
+    val kTypeName = this.asKTypeName()
     return XTypeName(jTypeName, kTypeName)
 }
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/DeclarationCollector.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/DeclarationCollector.kt
index db24932..a3a4713 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/DeclarationCollector.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/DeclarationCollector.kt
@@ -115,7 +115,7 @@
         return false
     }
     // check package
-    return packageName == closestMemberContainer.className.packageName()
+    return packageName == closestMemberContainer.asClassName().packageName
 }
 
 private fun XMethodElement.isStaticInterfaceMethod(): Boolean {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableElement.kt
index 9cc75c0..399c0bc7 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableElement.kt
@@ -31,7 +31,7 @@
      *   be an [XTypeElement].
      *   * When running with KSP, if this function is in source, the value will **NOT** be an
      *   [XTypeElement]. If you need the generated synthetic java class name, you can use
-     *   [XMemberContainer.className] property.
+     *   [XMemberContainer.asClassName] property.
      */
     override val enclosingElement: XMemberContainer
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFieldElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFieldElement.kt
index 0a7095d..27bd14d 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFieldElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XFieldElement.kt
@@ -32,7 +32,7 @@
      *   * When running with KSP, the value will **NOT** be an [XTypeElement]. It will
      *   be an [KspSyntheticFileMemberContainer] if this property is coming from the classpath or
      *   [KspFileMemberContainer] if this property is in source. If you need the generated
-     *   synthetic java class name, you can use [XMemberContainer.className] property.
+     *   synthetic java class name, you can use [XMemberContainer.asClassName] property.
      */
     override val enclosingElement: XMemberContainer
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XMemberContainer.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XMemberContainer.kt
index 1e51046..6d7cca4 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XMemberContainer.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XMemberContainer.kt
@@ -28,21 +28,20 @@
 interface XMemberContainer : XElement {
 
     override val name: String
-        get() = if (this is XTypeElement) this.name else className.simpleName()
+        get() = if (this is XTypeElement) this.name else asClassName().simpleNames.first()
 
     /**
      * The JVM ClassName for this container.
      *
      * For top level members of a Kotlin file, you can use this [ClassName] for code generation.
      */
-    // TODO(b/247248619): Deprecate when more progress is made, otherwise -werror fails the build.
-    // @Deprecated(
-    //     message = "Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
-    //     replaceWith = ReplaceWith(
-    //         expression = "asClassName().toJavaPoet()",
-    //         imports = ["androidx.room.compiler.codegen.toJavaPoet"]
-    //     )
-    // )
+     @Deprecated(
+         message = "Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
+         replaceWith = ReplaceWith(
+             expression = "asClassName().toJavaPoet()",
+             imports = ["androidx.room.compiler.codegen.toJavaPoet"]
+         )
+     )
     val className: ClassName
 
     /**
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt
index 9d3f1bb..efaa56e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt
@@ -158,6 +158,20 @@
 
     fun findType(klass: KClass<*>) = findType(klass.java.canonicalName!!)
 
+    fun requireTypeElement(typeName: XTypeName): XTypeElement {
+        if (typeName.isPrimitive) {
+            return requireTypeElement(typeName.java)
+        }
+        return when (backend) {
+            Backend.JAVAC -> requireTypeElement(typeName.java)
+            Backend.KSP -> {
+                val kClassName = typeName.kotlin as? KClassName
+                    ?: error("cannot find required type element ${typeName.kotlin}")
+                requireTypeElement(kClassName.canonicalName)
+            }
+        }
+    }
+
     fun requireTypeElement(typeName: TypeName) = requireTypeElement(typeName.toString())
 
     fun requireTypeElement(klass: KClass<*>) = requireTypeElement(klass.java.canonicalName!!)
@@ -166,6 +180,8 @@
 
     fun findTypeElement(klass: KClass<*>) = findTypeElement(klass.java.canonicalName!!)
 
+    fun getArrayType(typeName: XTypeName) = getArrayType(requireType(typeName))
+
     fun getArrayType(typeName: TypeName) = getArrayType(requireType(typeName))
 
     enum class Backend {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
index b79f94a..9256458 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
@@ -16,8 +16,9 @@
 
 package androidx.room.compiler.processing
 
+import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XTypeName
-import com.squareup.javapoet.ClassName
+import androidx.room.compiler.codegen.asClassName
 import com.squareup.javapoet.TypeName
 import kotlin.contracts.contract
 import kotlin.reflect.KClass
@@ -121,11 +122,6 @@
     fun boxed(): XType
 
     /**
-     * Returns `true` if this is a [List]
-     */
-    fun isList(): Boolean = isTypeOf(List::class)
-
-    /**
      * Returns `true` if this is the None type.
      */
     fun isNone(): Boolean
@@ -162,6 +158,11 @@
      * already [XNullability.NONNULL].
      */
     fun makeNonNullable(): XType
+
+    /**
+     * Returns true if this type is a type variable.
+     */
+    fun isTypeVariable(): Boolean
 }
 
 /**
@@ -174,14 +175,6 @@
     return this is XArrayType
 }
 
-/**
- * Returns true if this is a [List] or [Set].
- */
-// TODO(b/248280754): Move to room-compiler, overloaded function name
-fun XType.isCollection(): Boolean {
-    return isTypeOf(List::class) || isTypeOf(Set::class)
-}
-
 private fun isAssignableWithoutVariance(from: XType, to: XType): Boolean {
     val assignable = to.isAssignableFrom(from)
     if (assignable) {
@@ -218,36 +211,40 @@
 /**
  * Returns `true` if this is a primitive or boxed it
  */
-fun XType.isInt(): Boolean = typeName == TypeName.INT || typeName == KnownTypeNames.BOXED_INT
+fun XType.isInt(): Boolean = asTypeName() == XTypeName.PRIMITIVE_INT ||
+    asTypeName().equalsIgnoreNullability(KnownTypeNames.BOXED_INT)
 
 /**
  * Returns `true` if this is a primitive or boxed long
  */
-fun XType.isLong(): Boolean = typeName == TypeName.LONG || typeName == KnownTypeNames.BOXED_LONG
+fun XType.isLong(): Boolean = asTypeName() == XTypeName.PRIMITIVE_LONG ||
+    asTypeName().equalsIgnoreNullability(KnownTypeNames.BOXED_LONG)
+
 /**
  * Returns `true` if this is `void`
  */
-fun XType.isVoid() = typeName == TypeName.VOID
+fun XType.isVoid() = asTypeName() == XTypeName.UNIT_VOID
 
 /**
  * Returns `true` if this is a [Void]
  */
-fun XType.isVoidObject(): Boolean = typeName == KnownTypeNames.BOXED_VOID
+fun XType.isVoidObject(): Boolean = asTypeName().equalsIgnoreNullability(KnownTypeNames.BOXED_VOID)
 
 /**
  * Returns `true` if this is the kotlin [Unit] type.
  */
-fun XType.isKotlinUnit(): Boolean = typeName == KnownTypeNames.KOTLIN_UNIT
+fun XType.isKotlinUnit(): Boolean = asTypeName().equalsIgnoreNullability(KnownTypeNames.KOTLIN_UNIT)
 
 /**
  * Returns `true` if this represents a `byte`.
  */
-fun XType.isByte(): Boolean = typeName == TypeName.BYTE || typeName == KnownTypeNames.BOXED_BYTE
+fun XType.isByte(): Boolean = asTypeName() == XTypeName.PRIMITIVE_BYTE ||
+    asTypeName().equalsIgnoreNullability(KnownTypeNames.BOXED_BYTE)
 
 internal object KnownTypeNames {
-    val BOXED_VOID = TypeName.VOID.box()
-    val BOXED_INT = TypeName.INT.box()
-    val BOXED_LONG = TypeName.LONG.box()
-    val BOXED_BYTE = TypeName.BYTE.box()
-    val KOTLIN_UNIT = ClassName.get("kotlin", "Unit")
+    val BOXED_VOID = Void::class.asClassName()
+    val BOXED_INT = Int::class.asClassName()
+    val BOXED_LONG = Long::class.asClassName()
+    val BOXED_BYTE = Byte::class.asClassName()
+    val KOTLIN_UNIT = XClassName.get("kotlin", "Unit")
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
index 1faa32c9..0023e78 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
@@ -58,14 +58,13 @@
     /**
      * Javapoet [ClassName] of the type.
      */
-    // TODO(b/247248619): Deprecate when more progress is made, otherwise -werror fails the build.
-    // @Deprecated(
-    //     message = "Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
-    //     replaceWith = ReplaceWith(
-    //         expression = "asClassName().toJavaPoet()",
-    //         imports = ["androidx.room.compiler.codegen.toJavaPoet"]
-    //     )
-    // )
+     @Deprecated(
+         message = "Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
+         replaceWith = ReplaceWith(
+             expression = "asClassName().toJavaPoet()",
+             imports = ["androidx.room.compiler.codegen.toJavaPoet"]
+         )
+     )
     override val className: ClassName
 
     /**
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeParameterElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeParameterElement.kt
index 75bfaf8..e6bc9ca 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeParameterElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeParameterElement.kt
@@ -32,6 +32,7 @@
      */
     val bounds: List<XType>
 
+    // TODO(b/259091615): Migrate to XTypeName
     /** Returns the [TypeVariableName] for this type parameter) */
     val typeVariableName: TypeVariableName
 }
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
index 7457b785..e5463e1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
@@ -200,4 +200,6 @@
                 "TypeMirror#toXProcessing(XProcessingEnv)?"
         )
     }
+
+    override fun isTypeVariable() = typeMirror.kind == TypeKind.TYPEVAR
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index a2beb5a..1b50858 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -61,6 +61,13 @@
         element.qualifiedName.toString()
     }
 
+    @Deprecated(
+        "Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
+        replaceWith = ReplaceWith(
+            "asClassName().toJavaPoet()",
+            "androidx.room.compiler.codegen.toJavaPoet"
+        )
+    )
     override val className: ClassName by lazy {
         xClassName.java
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFileMemberContainer.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFileMemberContainer.kt
index ab94b8c..370d338 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFileMemberContainer.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFileMemberContainer.kt
@@ -44,6 +44,14 @@
         get() = null
     override val declaration: KSDeclaration?
         get() = null
+
+    @Deprecated(
+        "Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
+        replaceWith = ReplaceWith(
+            "asClassName().toJavaPoet()",
+            "androidx.room.compiler.codegen.toJavaPoet"
+        )
+    )
     override val className: ClassName by lazy {
         xClassName.java
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index 133b002..413bc62 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -95,7 +95,7 @@
     }
 
     override val superTypes: List<XType> by lazy {
-        if (typeName == TypeName.OBJECT) {
+        if (xTypeName == XTypeName.ANY_OBJECT) {
             // The object class doesn't have any supertypes.
             return@lazy emptyList<XType>()
         }
@@ -298,4 +298,6 @@
         }
         return copyWithNullability(XNullability.NONNULL)
     }
+
+    override fun isTypeVariable() = false
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
index 668554d..d4435f0 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
@@ -63,6 +63,8 @@
         return _extendsBound
     }
 
+    override fun isTypeVariable() = true
+
     override fun copyWithNullability(nullability: XNullability): KspTypeArgumentType {
         return KspTypeArgumentType(
             env = env,
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index 7674249..81d8218 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -121,6 +121,13 @@
         }
     }
 
+    @Deprecated(
+        "Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
+        replaceWith = ReplaceWith(
+            "asClassName().toJavaPoet()",
+            "androidx.room.compiler.codegen.toJavaPoet"
+        )
+    )
     override val className: ClassName by lazy {
         xClassName.java
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt
index 82addef..d584266 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt
@@ -49,6 +49,13 @@
     override val declaration: KSDeclaration?
         get() = null
 
+    @Deprecated(
+        "Use asClassName().toJavaPoet() to be clear the name is for JavaPoet.",
+        replaceWith = ReplaceWith(
+            "asClassName().toJavaPoet()",
+            "androidx.room.compiler.codegen.toJavaPoet"
+        )
+    )
     override val className: ClassName by lazy {
         xClassName.java
     }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt
index f325855..3cfe27c 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.compiler.codegen
 
+import androidx.room.compiler.processing.XNullability
 import com.google.common.truth.Truth.assertThat
 import com.squareup.kotlinpoet.INT
 import com.squareup.kotlinpoet.SHORT
@@ -131,4 +132,48 @@
         assertThat(expectedRawClass.parametrizedBy(String::class.asClassName()).rawTypeName)
             .isEqualTo(expectedRawClass)
     }
+
+    @Test
+    fun equalsIgnoreNullability() {
+        assertThat(
+            XTypeName.BOXED_INT.copy(nullable = false).equalsIgnoreNullability(
+                XTypeName.BOXED_INT.copy(nullable = true)
+            )
+        ).isTrue()
+
+        assertThat(
+            XTypeName.BOXED_INT.copy(nullable = false).equalsIgnoreNullability(
+                XTypeName.BOXED_LONG.copy(nullable = true)
+            )
+        ).isFalse()
+    }
+
+    @Test
+    fun toString_codeLanguage() {
+        assertThat(XTypeName.ANY_OBJECT.toString(CodeLanguage.JAVA))
+            .isEqualTo("java.lang.Object")
+        assertThat(XTypeName.ANY_OBJECT.toString(CodeLanguage.KOTLIN))
+            .isEqualTo("kotlin.Any")
+    }
+
+    @Test
+    fun mutations_kotlinUnavailable() {
+        val typeName = XClassName(
+            java = JClassName.get("test", "Foo"),
+            kotlin = XTypeName.UNAVAILABLE_KTYPE_NAME,
+            nullability = XNullability.UNKNOWN
+        )
+        assertThat(typeName.copy(nullable = true).kotlin)
+            .isEqualTo(XTypeName.UNAVAILABLE_KTYPE_NAME)
+        assertThat(typeName.copy(nullable = false).kotlin)
+            .isEqualTo(XTypeName.UNAVAILABLE_KTYPE_NAME)
+        assertThat(typeName.parametrizedBy(XTypeName.BOXED_LONG).kotlin)
+            .isEqualTo(XTypeName.UNAVAILABLE_KTYPE_NAME)
+        assertThat(XTypeName.getArrayName(typeName).kotlin)
+            .isEqualTo(XTypeName.UNAVAILABLE_KTYPE_NAME)
+        assertThat(XTypeName.getConsumerSuperName(typeName).kotlin)
+            .isEqualTo(XTypeName.UNAVAILABLE_KTYPE_NAME)
+        assertThat(XTypeName.getProducerExtendsName(typeName).kotlin)
+            .isEqualTo(XTypeName.UNAVAILABLE_KTYPE_NAME)
+    }
 }
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
index eddaa86..0d60682 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
@@ -144,12 +144,15 @@
         runProcessorTest(
             listOf(source)
         ) { invocation ->
-            PRIMITIVE_TYPES.flatMap {
-                listOf(it, it.box())
-            }.forEach {
-                val targetType = invocation.processingEnv.findType(it.toString())
-                assertThat(targetType?.typeName).isEqualTo(it)
-                assertThat(targetType?.boxed()?.typeName).isEqualTo(it.box())
+            PRIMITIVE_TYPES.zip(BOXED_PRIMITIVE_TYPES).forEach { (primitive, boxed) ->
+                val targetType = invocation.processingEnv.requireType(primitive)
+                assertThat(targetType.asTypeName()).isEqualTo(primitive)
+                assertThat(targetType.boxed().asTypeName()).isEqualTo(boxed)
+            }
+            BOXED_PRIMITIVE_TYPES.forEach { boxed ->
+                val targetType = invocation.processingEnv.requireType(boxed)
+                assertThat(targetType.asTypeName()).isEqualTo(boxed)
+                assertThat(targetType.boxed().asTypeName()).isEqualTo(boxed)
             }
         }
     }
@@ -317,14 +320,25 @@
 
     companion object {
         val PRIMITIVE_TYPES = listOf(
-            TypeName.BOOLEAN,
-            TypeName.BYTE,
-            TypeName.SHORT,
-            TypeName.INT,
-            TypeName.LONG,
-            TypeName.CHAR,
-            TypeName.FLOAT,
-            TypeName.DOUBLE,
+            XTypeName.PRIMITIVE_BOOLEAN,
+            XTypeName.PRIMITIVE_BYTE,
+            XTypeName.PRIMITIVE_SHORT,
+            XTypeName.PRIMITIVE_INT,
+            XTypeName.PRIMITIVE_LONG,
+            XTypeName.PRIMITIVE_CHAR,
+            XTypeName.PRIMITIVE_FLOAT,
+            XTypeName.PRIMITIVE_DOUBLE,
+        )
+
+        val BOXED_PRIMITIVE_TYPES = listOf(
+            XTypeName.BOXED_BOOLEAN,
+            XTypeName.BOXED_BYTE,
+            XTypeName.BOXED_SHORT,
+            XTypeName.BOXED_INT,
+            XTypeName.BOXED_LONG,
+            XTypeName.BOXED_CHAR,
+            XTypeName.BOXED_FLOAT,
+            XTypeName.BOXED_DOUBLE,
         )
     }
 }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index 54f581f..e6eeec0 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -28,6 +28,7 @@
 import androidx.room.compiler.processing.util.getDeclaredMethodByJvmName
 import androidx.room.compiler.processing.util.getField
 import androidx.room.compiler.processing.util.getMethodByJvmName
+import androidx.room.compiler.processing.util.isCollection
 import androidx.room.compiler.processing.util.javaElementUtils
 import androidx.room.compiler.processing.util.kspResolver
 import androidx.room.compiler.processing.util.runKspTest
@@ -1377,4 +1378,63 @@
             """.trimIndent()
         ))) { it.checkType() }
     }
+
+    @Test
+    fun isTypeVariable() {
+        val javaSubject = Source.java(
+            "test.JavaFoo",
+            """
+            package test;
+            class JavaFoo<T> {
+                T field;
+                T method(T param) {
+                    return null;
+                }
+            }
+            """.trimIndent()
+        )
+        val javaImplSubject = Source.java(
+            "test.JavaFooImpl",
+            """
+            package test;
+            class JavaFooImpl extends JavaFoo<String> {
+            }
+            """.trimIndent()
+        )
+        val kotlinSubject = Source.kotlin(
+            "Foo.kt",
+            """
+            package test
+            open class KotlinFoo<T> {
+                val field: T = TODO();
+                fun method(param: T): T {
+                    TODO()
+                }
+            }
+
+            class KotlinFooImpl : KotlinFoo<String>()
+            """.trimIndent()
+        )
+        runProcessorTest(
+            sources = listOf(javaSubject, javaImplSubject, kotlinSubject)
+        ) { invocation ->
+            listOf("test.JavaFoo", "test.KotlinFoo").forEach { fqn ->
+                val typeElement = invocation.processingEnv.requireTypeElement(fqn)
+                typeElement.getDeclaredField("field").let {
+                    assertThat(it.type.isTypeVariable()).isTrue()
+                    val asMemberOf =
+                        it.asMemberOf(invocation.processingEnv.requireType(fqn + "Impl"))
+                    assertThat(asMemberOf.isTypeVariable()).isFalse()
+                }
+                typeElement.getDeclaredMethodByJvmName("method").let {
+                    assertThat(it.returnType.isTypeVariable()).isTrue()
+                    assertThat(it.parameters.single().type.isTypeVariable()).isTrue()
+                    val asMemberOf =
+                        it.asMemberOf(invocation.processingEnv.requireType(fqn + "Impl"))
+                    assertThat(asMemberOf.returnType.isTypeVariable()).isFalse()
+                    assertThat(asMemberOf.parameterTypes.single().isTypeVariable()).isFalse()
+                }
+            }
+        }
+    }
 }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
index f14cebf..a2131bd 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
@@ -54,7 +54,7 @@
             fun XExecutableElement.createNewUniqueKey(
                 owner: String
             ): String {
-                val prefix = this.closestMemberContainer.className.canonicalName()
+                val prefix = this.closestMemberContainer.asClassName().canonicalName
                 val jvmName = if (this is XMethodElement) {
                     jvmName
                 } else {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt
index 7ff027c..2b5bb77 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt
@@ -56,10 +56,10 @@
             val className = elements.map {
                 val owner = invocation.kspResolver.getOwnerJvmClassName(it as KSPropertyDeclaration)
                 assertWithMessage(it.toString()).that(owner).isNotNull()
-                KspSyntheticFileMemberContainer(owner!!).className
+                KspSyntheticFileMemberContainer(owner!!).asClassName()
             }.first()
-            assertThat(className.packageName()).isEmpty()
-            assertThat(className.simpleNames()).containsExactly("AppKt")
+            assertThat(className.packageName).isEmpty()
+            assertThat(className.simpleNames).containsExactly("AppKt")
         }
     }
 
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestExtensions.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestExtensions.kt
index 2f897ca..23e4c3a 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestExtensions.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestExtensions.kt
@@ -17,6 +17,7 @@
 package androidx.room.compiler.processing.util
 
 import androidx.room.compiler.processing.XExecutableElement
+import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 
 fun XTypeElement.getAllFieldNames() = getAllFieldsIncludingPrivateSupers().map {
@@ -42,3 +43,7 @@
 fun XExecutableElement.getParameter(name: String) = parameters.firstOrNull {
     it.name == name
 } ?: throw AssertionError("cannot find parameter with name $name")
+
+fun XType.isCollection(): Boolean {
+    return isTypeOf(List::class) || isTypeOf(Set::class)
+}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
index 17d262b..f73b573 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
@@ -51,8 +51,7 @@
 
 object SupportDbTypeNames {
     val DB = XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteDatabase")
-    val SQLITE_STMT: XClassName =
-        XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteStatement")
+    val SQLITE_STMT = XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteStatement")
     val SQLITE_OPEN_HELPER = XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteOpenHelper")
     val SQLITE_OPEN_HELPER_CALLBACK =
         XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteOpenHelper", "Callback")
@@ -62,24 +61,17 @@
 }
 
 object RoomTypeNames {
-    val STRING_UTIL: XClassName = XClassName.get("$ROOM_PACKAGE.util", "StringUtil")
-    val ROOM_DB: XClassName = XClassName.get(ROOM_PACKAGE, "RoomDatabase")
+    val STRING_UTIL = XClassName.get("$ROOM_PACKAGE.util", "StringUtil")
+    val ROOM_DB = XClassName.get(ROOM_PACKAGE, "RoomDatabase")
     val ROOM_DB_KT = XClassName.get(ROOM_PACKAGE, "RoomDatabaseKt")
     val ROOM_DB_CALLBACK = XClassName.get(ROOM_PACKAGE, "RoomDatabase", "Callback")
     val ROOM_DB_CONFIG = XClassName.get(ROOM_PACKAGE, "DatabaseConfiguration")
-    val INSERTION_ADAPTER: XClassName =
-        XClassName.get(ROOM_PACKAGE, "EntityInsertionAdapter")
-    val UPSERTION_ADAPTER: XClassName =
-        XClassName.get(ROOM_PACKAGE, "EntityUpsertionAdapter")
-    val DELETE_OR_UPDATE_ADAPTER: XClassName =
-        XClassName.get(ROOM_PACKAGE, "EntityDeletionOrUpdateAdapter")
-    val SHARED_SQLITE_STMT: XClassName =
-        XClassName.get(ROOM_PACKAGE, "SharedSQLiteStatement")
+    val INSERTION_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityInsertionAdapter")
+    val UPSERTION_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityUpsertionAdapter")
+    val DELETE_OR_UPDATE_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityDeletionOrUpdateAdapter")
+    val SHARED_SQLITE_STMT = XClassName.get(ROOM_PACKAGE, "SharedSQLiteStatement")
     val INVALIDATION_TRACKER = XClassName.get(ROOM_PACKAGE, "InvalidationTracker")
-    val INVALIDATION_OBSERVER: ClassName =
-        ClassName.get("$ROOM_PACKAGE.InvalidationTracker", "Observer")
-    val ROOM_SQL_QUERY: XClassName =
-        XClassName.get(ROOM_PACKAGE, "RoomSQLiteQuery")
+    val ROOM_SQL_QUERY = XClassName.get(ROOM_PACKAGE, "RoomSQLiteQuery")
     val OPEN_HELPER = XClassName.get(ROOM_PACKAGE, "RoomOpenHelper")
     val OPEN_HELPER_DELEGATE = XClassName.get(ROOM_PACKAGE, "RoomOpenHelper", "Delegate")
     val OPEN_HELPER_VALIDATION_RESULT =
@@ -93,14 +85,11 @@
     val VIEW_INFO = XClassName.get("$ROOM_PACKAGE.util", "ViewInfo")
     val LIMIT_OFFSET_DATA_SOURCE: ClassName =
         ClassName.get("$ROOM_PACKAGE.paging", "LimitOffsetDataSource")
-    val DB_UTIL: XClassName =
-        XClassName.get("$ROOM_PACKAGE.util", "DBUtil")
-    val CURSOR_UTIL: XClassName =
-        XClassName.get("$ROOM_PACKAGE.util", "CursorUtil")
+    val DB_UTIL = XClassName.get("$ROOM_PACKAGE.util", "DBUtil")
+    val CURSOR_UTIL = XClassName.get("$ROOM_PACKAGE.util", "CursorUtil")
     val MIGRATION = XClassName.get("$ROOM_PACKAGE.migration", "Migration")
     val AUTO_MIGRATION_SPEC = XClassName.get("$ROOM_PACKAGE.migration", "AutoMigrationSpec")
-    val UUID_UTIL: XClassName =
-        XClassName.get("$ROOM_PACKAGE.util", "UUIDUtil")
+    val UUID_UTIL = XClassName.get("$ROOM_PACKAGE.util", "UUIDUtil")
     val AMBIGUOUS_COLUMN_RESOLVER = XClassName.get(ROOM_PACKAGE, "AmbiguousColumnResolver")
     val RELATION_UTIL = XClassName.get("androidx.room.util", "RelationUtil")
 }
@@ -131,9 +120,9 @@
 }
 
 object AndroidTypeNames {
-    val CURSOR: XClassName = XClassName.get("android.database", "Cursor")
+    val CURSOR = XClassName.get("android.database", "Cursor")
     val BUILD = XClassName.get("android.os", "Build")
-    val CANCELLATION_SIGNAL: XClassName = XClassName.get("android.os", "CancellationSignal")
+    val CANCELLATION_SIGNAL = XClassName.get("android.os", "CancellationSignal")
 }
 
 object CollectionTypeNames {
@@ -142,7 +131,14 @@
     val INT_SPARSE_ARRAY = XClassName.get(COLLECTION_PACKAGE, "SparseArrayCompat")
 }
 
+object KotlinCollectionMemberNames {
+    val ARRAY_OF_NULLS = XClassName.get("kotlin", "LibraryKt")
+        .packageMember("arrayOfNulls")
+}
+
 object CommonTypeNames {
+    val VOID = Void::class.asClassName()
+    val COLLECTION = Collection::class.asClassName()
     val LIST = List::class.asClassName()
     val MUTABLE_LIST = List::class.asMutableClassName()
     val ARRAY_LIST = XClassName.get("java.util", "ArrayList")
@@ -153,9 +149,8 @@
     val MUTABLE_SET = Set::class.asMutableClassName()
     val HASH_SET = XClassName.get("java.util", "HashSet")
     val STRING = String::class.asClassName()
-    val INTEGER = ClassName.get("java.lang", "Integer")
     val OPTIONAL = ClassName.get("java.util", "Optional")
-    val UUID = ClassName.get("java.util", "UUID")
+    val UUID = XClassName.get("java.util", "UUID")
     val BYTE_BUFFER = XClassName.get("java.nio", "ByteBuffer")
     val JAVA_CLASS = XClassName.get("java.lang", "Class")
 }
@@ -241,9 +236,9 @@
 }
 
 object KotlinTypeNames {
-    val UNIT = ClassName.get("kotlin", "Unit")
+    val ANY = Any::class.asClassName()
+    val UNIT = XClassName.get("kotlin", "Unit")
     val CONTINUATION = XClassName.get("kotlin.coroutines", "Continuation")
-    val COROUTINE_SCOPE = ClassName.get("kotlinx.coroutines", "CoroutineScope")
     val CHANNEL = ClassName.get("kotlinx.coroutines.channels", "Channel")
     val RECEIVE_CHANNEL = ClassName.get("kotlinx.coroutines.channels", "ReceiveChannel")
     val SEND_CHANNEL = ClassName.get("kotlinx.coroutines.channels", "SendChannel")
@@ -503,6 +498,18 @@
     else -> "arrayOf"
 }
 
+fun getToArrayFunction(type: XTypeName) = when (type) {
+    XTypeName.PRIMITIVE_BOOLEAN -> "toBooleanArray()"
+    XTypeName.PRIMITIVE_BYTE -> "toByteArray()"
+    XTypeName.PRIMITIVE_SHORT -> "toShortArray()"
+    XTypeName.PRIMITIVE_INT -> "toIntArray()"
+    XTypeName.PRIMITIVE_LONG -> "toLongArray()"
+    XTypeName.PRIMITIVE_CHAR -> "toCharArray()"
+    XTypeName.PRIMITIVE_FLOAT -> "toFloatArray()"
+    XTypeName.PRIMITIVE_DOUBLE -> "toDoubleArray()"
+    else -> error("Provided type expected to be primitive. Found: $type")
+}
+
 /**
  * Code of expression for [Collection.size] in Kotlin, and [java.util.Collection.size] for Java.
  */
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt
index 1bb79c5..0d8e62e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xtype_ext.kt
@@ -16,7 +16,7 @@
 
 package androidx.room.ext
 
-import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.isArray
 import androidx.room.compiler.processing.isByte
@@ -24,9 +24,6 @@
 import androidx.room.compiler.processing.isKotlinUnit
 import androidx.room.compiler.processing.isVoid
 import androidx.room.compiler.processing.isVoidObject
-import androidx.room.ext.CommonTypeNames.BYTE_BUFFER
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
 
 /**
  * Returns `true` if this type is not the `void` type.
@@ -61,12 +58,12 @@
 /**
  * Returns `true` if this is a `ByteBuffer` type.
  */
-fun XType.isByteBuffer(): Boolean = asTypeName() == BYTE_BUFFER
+fun XType.isByteBuffer() = asTypeName().equalsIgnoreNullability(CommonTypeNames.BYTE_BUFFER)
 
 /**
  * Returns `true` if this represents a `UUID` type.
  */
-fun XType.isUUID(): Boolean = typeName == CommonTypeNames.UUID
+fun XType.isUUID() = asTypeName().equalsIgnoreNullability(CommonTypeNames.UUID)
 
 /**
  * Checks if the class of the provided type has the equals() and hashCode() methods declared.
@@ -81,7 +78,7 @@
     if (this.isSupportedMapTypeArg()) return true
 
     val typeElement = this.typeElement ?: return false
-    if (typeElement.className == ClassName.OBJECT) {
+    if (typeElement.asClassName().equalsIgnoreNullability(XTypeName.ANY_OBJECT)) {
         return false
     }
 
@@ -90,13 +87,13 @@
     }
     val hasEquals = typeElement.getDeclaredMethods().any {
         it.jvmName == "equals" &&
-            it.returnType.typeName == TypeName.BOOLEAN &&
+            it.returnType.asTypeName() == XTypeName.PRIMITIVE_BOOLEAN &&
             it.parameters.count() == 1 &&
-            it.parameters[0].type.typeName == TypeName.OBJECT
+            it.parameters[0].type.asTypeName().equalsIgnoreNullability(XTypeName.ANY_OBJECT)
     }
     val hasHashCode = typeElement.getDeclaredMethods().any {
         it.jvmName == "hashCode" &&
-            it.returnType.typeName == TypeName.INT &&
+            it.returnType.asTypeName() == XTypeName.PRIMITIVE_INT &&
             it.parameters.count() == 0
     }
 
@@ -110,12 +107,24 @@
  * Map or Multimap return type.
  */
 fun XType.isSupportedMapTypeArg(): Boolean {
-    if (this.typeName.isPrimitive) return true
-    if (this.typeName.isBoxedPrimitive) return true
-    if (this.typeName == CommonTypeNames.STRING.toJavaPoet()) return true
+    if (this.asTypeName().isPrimitive) return true
+    if (this.asTypeName().isBoxedPrimitive) return true
+    if (this.asTypeName().equalsIgnoreNullability(CommonTypeNames.STRING)) return true
     if (this.isTypeOf(ByteArray::class)) return true
     if (this.isArray() && this.isByte()) return true
     val typeElement = this.typeElement ?: return false
     if (typeElement.isEnum()) return true
     return false
 }
+
+/**
+ * Returns `true` if this is a [List]
+ */
+fun XType.isList(): Boolean = isTypeOf(List::class)
+
+/**
+ * Returns true if this is a [List] or [Set].
+ */
+fun XType.isCollection(): Boolean {
+    return isTypeOf(List::class) || isTypeOf(Set::class)
+}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt b/room/room-compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
index f8266b0..8af2da7 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
@@ -17,13 +17,11 @@
 package androidx.room.parser
 
 import androidx.room.ColumnInfo
-import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XProcessingEnv
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.CommonTypeNames
 import androidx.room.parser.expansion.isCoreSelect
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.TypeName
 import java.util.Locale
 import org.antlr.v4.runtime.tree.ParseTree
 import org.antlr.v4.runtime.tree.TerminalNode
@@ -269,13 +267,20 @@
 
     fun getTypeMirrors(env: XProcessingEnv): List<XType>? {
         return when (this) {
-            TEXT -> withBoxedAndNullableTypes(env, CommonTypeNames.STRING.toJavaPoet())
-            INTEGER -> withBoxedAndNullableTypes(
-                env, TypeName.INT, TypeName.BYTE, TypeName.CHAR,
-                TypeName.LONG, TypeName.SHORT
+            TEXT -> withBoxedAndNullableTypes(env, CommonTypeNames.STRING)
+            INTEGER -> withBoxedAndNullableTypes(env,
+                XTypeName.PRIMITIVE_INT, XTypeName.PRIMITIVE_BYTE, XTypeName.PRIMITIVE_CHAR,
+                XTypeName.PRIMITIVE_LONG, XTypeName.PRIMITIVE_SHORT
             )
-            REAL -> withBoxedAndNullableTypes(env, TypeName.DOUBLE, TypeName.FLOAT)
-            BLOB -> withBoxedAndNullableTypes(env, ArrayTypeName.of(TypeName.BYTE))
+
+            REAL -> withBoxedAndNullableTypes(env,
+                XTypeName.PRIMITIVE_DOUBLE, XTypeName.PRIMITIVE_FLOAT
+            )
+
+            BLOB -> withBoxedAndNullableTypes(env,
+                XTypeName.getArrayName(XTypeName.PRIMITIVE_BYTE)
+            )
+
             else -> null
         }
     }
@@ -289,7 +294,7 @@
      */
     private fun withBoxedAndNullableTypes(
         env: XProcessingEnv,
-        vararg typeNames: TypeName
+        vararg typeNames: XTypeName
     ): List<XType> {
         return typeNames.flatMap { typeName ->
             sequence {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt b/room/room-compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt
index 3d0466c..f91c0b4 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt
@@ -17,10 +17,8 @@
 package androidx.room.preconditions
 
 import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XType
 import androidx.room.log.RLog
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeVariableName
 import kotlin.contracts.contract
 import kotlin.reflect.KClass
 
@@ -62,16 +60,15 @@
     }
 
     fun notUnbound(
-        typeName: TypeName,
+        type: XType,
         element: XElement,
         errorMsg: String,
         vararg args: Any
     ): Boolean {
         // TODO support bounds cases like <T extends Foo> T bar()
-        val failed = check(typeName !is TypeVariableName, element, errorMsg, args)
-        if (typeName is ParameterizedTypeName) {
-            val nestedFailure = typeName.typeArguments
-                .any { notUnbound(it, element, errorMsg, args) }
+        val failed = check(!type.isTypeVariable(), element, errorMsg, args)
+        if (type.typeArguments.isNotEmpty()) {
+            val nestedFailure = type.typeArguments.any { notUnbound(it, element, errorMsg, args) }
             return !(failed || nestedFailure)
         }
         return !failed
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
index 543335a..071aab0 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
@@ -79,7 +79,7 @@
             if (!implementsMigrationSpec) {
                 context.logger.e(
                     typeElement,
-                    autoMigrationElementMustImplementSpec(typeElement.className.simpleName())
+                    autoMigrationElementMustImplementSpec(typeElement.asClassName().canonicalName)
                 )
                 return null
             }
@@ -98,7 +98,7 @@
             return null
         }
 
-        val specClassName = specElement?.className?.simpleName()
+        val specClassName = specElement?.asClassName()?.simpleNames?.first()
         val deleteColumnEntries = specElement?.let { element ->
             element.getAnnotations(DeleteColumn::class).map {
                 AutoMigration.DeletedColumn(
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 a3bc71c..fcca5f5 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
@@ -18,10 +18,10 @@
 
 import androidx.room.RewriteQueriesToDropUnusedColumns
 import androidx.room.compiler.codegen.CodeLanguage
-import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.XElement
 import androidx.room.compiler.processing.XProcessingEnv
 import androidx.room.compiler.processing.XType
+import androidx.room.ext.CommonTypeNames
 import androidx.room.log.RLog
 import androidx.room.parser.expansion.ProjectionExpander
 import androidx.room.parser.optimization.RemoveUnusedColumnQueryRewriter
@@ -131,19 +131,19 @@
 
     class CommonTypes(val processingEnv: XProcessingEnv) {
         val VOID: XType by lazy {
-            processingEnv.requireType("java.lang.Void")
+            processingEnv.requireType(CommonTypeNames.VOID)
         }
         val STRING: XType by lazy {
-            processingEnv.requireType(String::class.asClassName())
+            processingEnv.requireType(CommonTypeNames.STRING)
         }
         val READONLY_COLLECTION: XType by lazy {
-            processingEnv.requireType(Collection::class.asClassName())
+            processingEnv.requireType(CommonTypeNames.COLLECTION)
         }
         val LIST: XType by lazy {
-            processingEnv.requireType(List::class.asClassName())
+            processingEnv.requireType(CommonTypeNames.LIST)
         }
         val SET: XType by lazy {
-            processingEnv.requireType(Set::class.asClassName())
+            processingEnv.requireType(CommonTypeNames.SET)
         }
     }
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
index ad2acae..6ee5a37 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
@@ -59,7 +59,9 @@
                 if (typeElement == null) {
                     context.logger.e(
                         element,
-                        ProcessorErrors.typeConverterMustBeDeclared(it.typeName)
+                        ProcessorErrors.typeConverterMustBeDeclared(
+                            it.asTypeName().toString(context.codeLanguage)
+                        )
                     )
                     emptyList()
                 } else {
@@ -84,7 +86,7 @@
 
         private fun reportDuplicates(context: Context, converters: List<CustomTypeConverter>) {
             converters
-                .groupBy { it.from.typeName to it.to.typeName }
+                .groupBy { it.from.asTypeName() to it.to.asTypeName() }
                 .filterValues { it.size > 1 }
                 .values.forEach { possiblyDuplicateConverters ->
                     possiblyDuplicateConverters.forEach { converter ->
@@ -157,9 +159,8 @@
             context.logger.e(methodElement, TYPE_CONVERTER_BAD_RETURN_TYPE)
             return null
         }
-        val returnTypeName = returnType.typeName
         context.checker.notUnbound(
-            returnTypeName, methodElement,
+            returnType, methodElement,
             TYPE_CONVERTER_UNBOUND_GENERIC
         )
         val params = methodElement.parameters
@@ -170,7 +171,7 @@
         val param = params.map {
             it.asMemberOf(container.type)
         }.first()
-        context.checker.notUnbound(param.typeName, params[0], TYPE_CONVERTER_UNBOUND_GENERIC)
+        context.checker.notUnbound(param, params[0], TYPE_CONVERTER_UNBOUND_GENERIC)
         return CustomTypeConverter(
             enclosingClass = container,
             isEnclosingClassKotlinObject = isContainerKotlinObject,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
index a0f36c1..fb8cb9e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
@@ -223,9 +223,8 @@
             null
         }
 
-        val type = declaredType.typeName
         context.checker.notUnbound(
-            type, element,
+            declaredType, element,
             ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES
         )
 
@@ -256,7 +255,7 @@
             context.logger.e(
                 element,
                 ProcessorErrors.daoMustHaveMatchingConstructor(
-                    element.qualifiedName, dbType.typeName.toString()
+                    element.qualifiedName, dbType.asTypeName().toString(context.codeLanguage)
                 )
             )
         }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index 2c15c2a..039ce5a 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -18,12 +18,12 @@
 
 import androidx.room.AutoMigration
 import androidx.room.SkipQueryVerification
-import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XAnnotationBox
 import androidx.room.compiler.processing.XElement
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
-import androidx.room.ext.RoomTypeNames.ROOM_DB
+import androidx.room.ext.RoomTypeNames
 import androidx.room.migration.bundle.DatabaseBundle
 import androidx.room.migration.bundle.SchemaBundle
 import androidx.room.processor.ProcessorErrors.AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF
@@ -42,7 +42,6 @@
 import androidx.room.vo.Warning
 import androidx.room.vo.columnNames
 import androidx.room.vo.findFieldByColumnName
-import com.squareup.javapoet.TypeName
 import java.io.File
 import java.io.FileInputStream
 import java.nio.file.Path
@@ -52,9 +51,7 @@
     val context = baseContext.fork(element)
 
     val roomDatabaseType: XType by lazy {
-        context.processingEnv.requireType(
-            ROOM_DB.toJavaPoet().packageName() + "." + ROOM_DB.toJavaPoet().simpleName()
-        )
+        context.processingEnv.requireType(RoomTypeNames.ROOM_DB)
     }
 
     fun process(): Database {
@@ -94,7 +91,7 @@
             it.isAbstract()
         }.filterNot {
             // remove methods that belong to room
-            it.enclosingElement.className == ROOM_DB.toJavaPoet()
+            it.enclosingElement.asClassName() == RoomTypeNames.ROOM_DB
         }.mapNotNull { executable ->
             // TODO when we add support for non Dao return types (e.g. database), this code needs
             // to change
@@ -168,7 +165,7 @@
         return autoMigrationList.mapNotNull {
             val databaseSchemaFolderPath = Path.of(
                 context.schemaOutFolderPath!!,
-                element.className.canonicalName()
+                element.asClassName().canonicalName
             )
             val autoMigration = it.value
             val validatedFromSchemaFile = getValidatedSchemaFile(
@@ -319,13 +316,13 @@
             .filter { it.value.size > 1 } // get the ones with duplicate names
             .forEach {
                 // do not report duplicates from the same entity
-                if (it.value.distinctBy { it.second.typeName.toJavaPoet() }.size > 1) {
+                if (it.value.distinctBy { it.second.typeName }.size > 1) {
                     context.logger.e(
                         element,
                         ProcessorErrors.duplicateIndexInDatabase(
                             it.key,
                             it.value.map {
-                                "${it.second.typeName.toJavaPoet()} > ${it.first}"
+                                "${it.second.typeName.toString(context.codeLanguage)} > ${it.first}"
                             }
                         )
                     )
@@ -338,11 +335,11 @@
         daoMethods: List<DaoMethod>,
         entities: List<Entity>
     ) {
-        val entityTypeNames = entities.map { it.typeName.toJavaPoet() }.toSet()
+        val entityTypeNames = entities.map { it.typeName }.toSet()
         daoMethods.groupBy { it.dao.typeName }
             .forEach {
                 if (it.value.size > 1) {
-                    val error = ProcessorErrors.duplicateDao(it.key.toJavaPoet(),
+                    val error = ProcessorErrors.duplicateDao(it.key.toString(context.codeLanguage),
                         it.value.map { it.element.jvmName }
                     )
                     it.value.forEach { daoMethod ->
@@ -358,7 +355,7 @@
         val check = fun(
             element: XElement,
             dao: Dao,
-            typeName: TypeName?
+            typeName: XTypeName?
         ) {
             typeName?.let {
                 if (!entityTypeNames.contains(typeName)) {
@@ -366,8 +363,8 @@
                         element,
                         ProcessorErrors.shortcutEntityIsNotInDatabase(
                             database = dbElement.qualifiedName,
-                            dao = dao.typeName.toJavaPoet().toString(),
-                            entity = typeName.toString()
+                            dao = dao.typeName.toString(context.codeLanguage),
+                            entity = typeName.toString(context.codeLanguage)
                         )
                     )
                 }
@@ -376,12 +373,12 @@
         daoMethods.forEach { daoMethod ->
             daoMethod.dao.deleteOrUpdateShortcutMethods.forEach { method ->
                 method.entities.forEach {
-                    check(method.element, daoMethod.dao, it.value.entityTypeName.toJavaPoet())
+                    check(method.element, daoMethod.dao, it.value.entityTypeName)
                 }
             }
             daoMethod.dao.insertOrUpsertShortcutMethods.forEach { method ->
                 method.entities.forEach {
-                    check(method.element, daoMethod.dao, it.value.entityTypeName.toJavaPoet())
+                    check(method.element, daoMethod.dao, it.value.entityTypeName)
                 }
             }
         }
@@ -395,14 +392,14 @@
         val entitiesInfo = entities.map {
             Triple(
                 it.tableName.lowercase(Locale.US),
-                it.typeName.toJavaPoet().toString(),
+                it.typeName.toString(context.codeLanguage),
                 it.element
             )
         }
         val viewsInfo = views.map {
             Triple(
                 it.viewName.lowercase(Locale.US),
-                it.typeName.toJavaPoet().toString(),
+                it.typeName.toString(context.codeLanguage),
                 it.element
             )
         }
@@ -456,7 +453,7 @@
                 context.logger.e(
                     element,
                     ProcessorErrors.invalidEntityTypeInDatabaseAnnotation(
-                        it.typeName
+                        it.asTypeName().toString(context.codeLanguage)
                     )
                 )
                 null
@@ -476,7 +473,7 @@
                 context.logger.e(
                     element,
                     ProcessorErrors.invalidViewTypeInDatabaseAnnotation(
-                        it.typeName
+                        it.asTypeName().toString(context.codeLanguage)
                     )
                 )
                 null
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt
index 3325e6f..1645d65 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/FieldProcessor.kt
@@ -36,7 +36,6 @@
     val context = baseContext.fork(element)
     fun process(): Field {
         val member = element.asMemberOf(containing)
-        val type = member.typeName
         val columnInfo = element.getAnnotation(ColumnInfo::class)?.value
         val name = element.name
         val rawCName = if (columnInfo != null && columnInfo.name != ColumnInfo.INHERIT_FIELD_NAME) {
@@ -56,7 +55,7 @@
             ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY
         )
         context.checker.notUnbound(
-            type, element,
+            member, element,
             ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS
         )
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt
index 6d9fc0c..787cacf 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt
@@ -176,7 +176,7 @@
             context.logger.e(
                 contentEntityElement,
                 ProcessorErrors.externalContentNotAnEntity(
-                    contentEntityElement.className.canonicalName()
+                    contentEntityElement.asClassName().canonicalName
                 )
             )
             return null
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/InsertionMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/InsertionMethodProcessor.kt
index f75be97..592abec 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/InsertionMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/InsertionMethodProcessor.kt
@@ -20,7 +20,6 @@
 
 import androidx.room.Insert
 import androidx.room.OnConflictStrategy
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
 import androidx.room.vo.InsertionMethod
@@ -47,9 +46,8 @@
         )
 
         val returnType = delegate.extractReturnType()
-        val returnTypeName = returnType.typeName
         context.checker.notUnbound(
-            returnTypeName, executableElement,
+            returnType, executableElement,
             ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS
         )
 
@@ -64,7 +62,7 @@
                     entity.primaryKey.autoGenerateId || !missingPrimaryKeys,
                     executableElement,
                     ProcessorErrors.missingPrimaryKeysInPartialEntityForInsert(
-                        partialEntityName = pojo.typeName.toJavaPoet().toString(),
+                        partialEntityName = pojo.typeName.toString(context.codeLanguage),
                         primaryKeyNames = entity.primaryKey.fields.columnNames
                     )
                 )
@@ -79,7 +77,7 @@
                     missingRequiredFields.isEmpty(),
                     executableElement,
                     ProcessorErrors.missingRequiredColumnsInPartialEntity(
-                        partialEntityName = pojo.typeName.toJavaPoet().toString(),
+                        partialEntityName = pojo.typeName.toString(context.codeLanguage),
                         missingColumnNames = missingRequiredFields.map { it.columnName }
                     )
                 )
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
index 6252b56..0f67cea 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
@@ -22,14 +22,13 @@
 import androidx.room.Junction
 import androidx.room.PrimaryKey
 import androidx.room.Relation
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XExecutableElement
 import androidx.room.compiler.processing.XFieldElement
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.XVariableElement
-import androidx.room.compiler.processing.isCollection
 import androidx.room.compiler.processing.isVoid
+import androidx.room.ext.isCollection
 import androidx.room.ext.isNotVoid
 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD
@@ -512,7 +511,7 @@
             context.logger.e(
                 relationElement,
                 ProcessorErrors.relationCannotFindEntityField(
-                    entityName = entity.typeName.toJavaPoet().toString(),
+                    entityName = entity.typeName.toString(context.codeLanguage),
                     columnName = annotation.value.entityColumn,
                     availableColumns = entity.columnNames
                 )
@@ -558,7 +557,7 @@
                         context.logger.w(
                             Warning.MISSING_INDEX_ON_JUNCTION, field.element,
                             ProcessorErrors.junctionColumnWithoutIndex(
-                                entityName = entityOrView.typeName.toJavaPoet().toString(),
+                                entityName = entityOrView.typeName.toString(context.codeLanguage),
                                 columnName = columnName
                             )
                         )
@@ -578,7 +577,7 @@
                     context.logger.e(
                         junctionElement,
                         ProcessorErrors.relationCannotFindJunctionParentField(
-                            entityName = entityOrView.typeName.toJavaPoet().toString(),
+                            entityName = entityOrView.typeName.toString(context.codeLanguage),
                             columnName = junctionParentColumn,
                             availableColumns = entityOrView.columnNames
                         )
@@ -597,7 +596,7 @@
                     context.logger.e(
                         junctionElement,
                         ProcessorErrors.relationCannotFindJunctionEntityField(
-                            entityName = entityOrView.typeName.toJavaPoet().toString(),
+                            entityName = entityOrView.typeName.toString(context.codeLanguage),
                             columnName = junctionEntityColumn,
                             availableColumns = entityOrView.columnNames
                         )
@@ -654,7 +653,7 @@
             context.logger.e(
                 relationElement,
                 ProcessorErrors.relationBadProject(
-                    entity.typeName.toJavaPoet().toString(),
+                    entity.typeName.toString(context.codeLanguage),
                     missingColumns, entity.columnNames
                 )
             )
@@ -677,7 +676,7 @@
         entityField: Field,
         typeArgElement: XTypeElement
     ): List<String> {
-        return if (inferEntity || typeArg.typeName == entity.typeName.toJavaPoet()) {
+        return if (inferEntity || typeArg.asTypeName() == entity.typeName) {
             entity.columnNames
         } else {
             val columnAdapter = context.typeAdapterStore.findCursorValueReader(typeArg, null)
@@ -780,9 +779,9 @@
                 element = field.element,
                 msg = ProcessorErrors.mismatchedGetter(
                     fieldName = field.name,
-                    ownerType = element.type.typeName,
-                    getterType = field.getter.type.typeName,
-                    fieldType = field.typeName.toJavaPoet()
+                    ownerType = element.type.asTypeName().toString(context.codeLanguage),
+                    getterType = field.getter.type.asTypeName().toString(context.codeLanguage),
+                    fieldType = field.typeName.toString(context.codeLanguage)
                 )
             )
             field.statementBinder = context.typeAdapterStore.findStatementValueBinder(
@@ -864,9 +863,9 @@
                 element = field.element,
                 msg = ProcessorErrors.mismatchedSetter(
                     fieldName = field.name,
-                    ownerType = element.type.typeName,
-                    setterType = field.setter.type.typeName,
-                    fieldType = field.typeName.toJavaPoet()
+                    ownerType = element.type.asTypeName().toString(context.codeLanguage),
+                    setterType = field.setter.type.asTypeName().toString(context.codeLanguage),
+                    fieldType = field.typeName.toString(context.codeLanguage)
                 )
             )
             field.cursorValueReader = context.typeAdapterStore.findCursorValueReader(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index 9cc2640..ca1602c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -23,7 +23,6 @@
 import androidx.room.RewriteQueriesToDropUnusedColumns
 import androidx.room.Update
 import androidx.room.Upsert
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.ext.KotlinTypeNames
 import androidx.room.ext.RoomTypeNames.ROOM_DB
 import androidx.room.ext.SupportDbTypeNames
@@ -31,7 +30,6 @@
 import androidx.room.parser.SQLTypeAffinity
 import androidx.room.vo.CustomTypeConverter
 import androidx.room.vo.Field
-import com.squareup.javapoet.TypeName
 
 object ProcessorErrors {
     private fun String.trim(): String {
@@ -138,7 +136,7 @@
     val QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE = "Query/Insert method parameters cannot " +
         "start with underscore (_)."
 
-    fun cannotFindQueryResultAdapter(returnTypeName: TypeName) = "Not sure how to convert a " +
+    fun cannotFindQueryResultAdapter(returnTypeName: String) = "Not sure how to convert a " +
         "Cursor to this method's return type ($returnTypeName)."
 
     fun classMustImplementEqualsAndHashCode(keyType: String) = "The key" +
@@ -161,14 +159,14 @@
     val MAP_INFO_MUST_HAVE_AT_LEAST_ONE_COLUMN_PROVIDED = "To use the @MapInfo annotation, you " +
         "must provide either the key column name, value column name, or both."
 
-    fun keyMayNeedMapInfo(keyArg: TypeName): String {
+    fun keyMayNeedMapInfo(keyArg: String): String {
         return """
             Looks like you may need to use @MapInfo to clarify the 'keyColumn' needed for
             the return type of a method. Type argument that needs @MapInfo: $keyArg
             """.trim()
     }
 
-    fun valueMayNeedMapInfo(valueArg: TypeName): String {
+    fun valueMayNeedMapInfo(valueArg: String): String {
         return """
             Looks like you may need to use @MapInfo to clarify the 'valueColumn' needed for
             the return type of a method. Type argument that needs @MapInfo: $valueArg
@@ -226,7 +224,7 @@
         "annotated with @Entity or a collection/array of it."
 
     val DB_MUST_EXTEND_ROOM_DB = "Classes annotated with @Database should extend " +
-        ROOM_DB.toJavaPoet()
+        ROOM_DB.canonicalName
 
     val OBSERVABLE_QUERY_NOTHING_TO_OBSERVE = "Observable query return type (LiveData, Flowable" +
         ", DataSource, DataSourceFactory etc) can only be used with SELECT queries that" +
@@ -267,7 +265,7 @@
         return MISSING_PARAMETER_FOR_BIND.format(bindVarName.joinToString(", "))
     }
 
-    fun valueCollectionMustBeListOrSet(mapValueTypeName: TypeName): String {
+    fun valueCollectionMustBeListOrSet(mapValueTypeName: String): String {
         return "Multimap 'value' collection type must be a List or Set. Found $mapValueTypeName."
     }
 
@@ -288,7 +286,7 @@
 
     val DAO_METHOD_CONFLICTS_WITH_OTHERS = "Dao method has conflicts."
 
-    fun duplicateDao(dao: TypeName, methodNames: List<String>): String {
+    fun duplicateDao(dao: String, methodNames: List<String>): String {
         return """
                 All of these functions [${methodNames.joinToString(", ")}] return the same DAO
                 class [$dao].
@@ -300,7 +298,7 @@
     }
 
     fun pojoMissingNonNull(
-        pojoTypeName: TypeName,
+        pojoTypeName: String,
         missingPojoFields: List<String>,
         allQueryColumns: List<String>
     ): String {
@@ -313,10 +311,10 @@
     }
 
     fun cursorPojoMismatch(
-        pojoTypeNames: List<TypeName>,
+        pojoTypeNames: List<String>,
         unusedColumns: List<String>,
         allColumns: List<String>,
-        pojoUnusedFields: Map<TypeName, List<Field>>,
+        pojoUnusedFields: Map<String, List<Field>>,
     ): String {
         val unusedColumnsWarning = if (unusedColumns.isNotEmpty()) {
             val pojoNames = if (pojoTypeNames.size > 1) {
@@ -368,7 +366,7 @@
             " ${converters.joinToString(", ") { it.toString() }}"
     }
 
-    fun typeConverterMustBeDeclared(typeName: TypeName): String {
+    fun typeConverterMustBeDeclared(typeName: String): String {
         return "Invalid type converter type: $typeName. Type converters must be a class."
     }
 
@@ -713,7 +711,7 @@
 
     val RAW_QUERY_BAD_RETURN_TYPE = "RawQuery methods must return a non-void type."
 
-    fun rawQueryBadEntity(typeName: TypeName): String {
+    fun rawQueryBadEntity(typeName: String): String {
         return """
             observedEntities field in RawQuery must either reference a class that is annotated
             with @Entity or it should reference a POJO that either contains @Embedded fields that
@@ -822,7 +820,7 @@
         "perform the query."
 
     fun cannotFindPreparedQueryResultAdapter(
-        returnType: TypeName,
+        returnType: String,
         type: QueryType
     ) = StringBuilder().apply {
         append("Not sure how to handle query method's return type ($returnType). ")
@@ -856,9 +854,9 @@
 
     fun mismatchedGetter(
         fieldName: String,
-        ownerType: TypeName,
-        getterType: TypeName,
-        fieldType: TypeName
+        ownerType: String,
+        getterType: String,
+        fieldType: String
     ) = """
             $ownerType's $fieldName field has type $fieldType but its getter returns $getterType.
             This mismatch might cause unexpected $fieldName values in the database when $ownerType
@@ -867,9 +865,9 @@
 
     fun mismatchedSetter(
         fieldName: String,
-        ownerType: TypeName,
-        setterType: TypeName,
-        fieldType: TypeName
+        ownerType: String,
+        setterType: String,
+        fieldType: String
     ) = """
             $ownerType's $fieldName field has type $fieldType but its setter accepts $setterType.
             This mismatch might cause unexpected $fieldName values when $ownerType is read from the
@@ -879,11 +877,11 @@
     val DATABASE_INVALID_DAO_METHOD_RETURN_TYPE = "Abstract database methods must return a @Dao " +
         "annotated class or interface."
 
-    fun invalidEntityTypeInDatabaseAnnotation(typeName: TypeName): String {
+    fun invalidEntityTypeInDatabaseAnnotation(typeName: String): String {
         return "Invalid Entity type: $typeName. An entity in the database must be a class."
     }
 
-    fun invalidViewTypeInDatabaseAnnotation(typeName: TypeName): String {
+    fun invalidViewTypeInDatabaseAnnotation(typeName: String): String {
         return "Invalid View type: $typeName. Views in a database must be a class or an " +
             "interface."
     }
@@ -899,7 +897,7 @@
         "or an interface."
 
     fun shortcutMethodArgumentMustBeAClass(
-        typeName: TypeName
+        typeName: String
     ): String {
         return "Invalid query argument: $typeName. It must be a class or an interface."
     }
@@ -1100,7 +1098,7 @@
     fun ambiguousColumn(
         columnName: String,
         location: AmbiguousColumnLocation,
-        typeName: TypeName?
+        typeName: String?
     ): String {
         val (locationDesc, recommendation) = when (location) {
             AmbiguousColumnLocation.MAP_INFO -> {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
index e78d084..d9333f1 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
@@ -19,7 +19,6 @@
 import androidx.room.Query
 import androidx.room.SkipQueryVerification
 import androidx.room.Transaction
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XAnnotationBox
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
@@ -134,9 +133,8 @@
             ParsedQuery.MISSING
         }
 
-        val returnTypeName = returnType.typeName
         context.checker.notUnbound(
-            returnTypeName, executableElement,
+            returnType, executableElement,
             ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS
         )
 
@@ -194,7 +192,10 @@
         context.checker.check(
             resultBinder.adapter != null,
             executableElement,
-            ProcessorErrors.cannotFindPreparedQueryResultAdapter(returnType.typeName, query.type)
+            ProcessorErrors.cannotFindPreparedQueryResultAdapter(
+                returnType.asTypeName().toString(context.codeLanguage),
+                query.type
+            )
         )
 
         val parameters = delegate.extractQueryParams(query)
@@ -220,7 +221,9 @@
         context.checker.check(
             resultBinder.adapter != null,
             executableElement,
-            ProcessorErrors.cannotFindQueryResultAdapter(returnType.typeName)
+            ProcessorErrors.cannotFindQueryResultAdapter(
+                returnType.asTypeName().toString(context.codeLanguage)
+            )
         )
 
         val inTransaction = executableElement.hasAnnotation(Transaction::class)
@@ -251,10 +254,12 @@
             val pojoMappings = mappings.filterIsInstance<PojoRowAdapter.PojoMapping>()
             val pojoUnusedFields = pojoMappings
                 .filter { it.unusedFields.isNotEmpty() }
-                .associate { it.pojo.typeName.toJavaPoet() to it.unusedFields }
+                .associate { it.pojo.typeName.toString(context.codeLanguage) to it.unusedFields }
             if (unusedColumns.isNotEmpty() || pojoUnusedFields.isNotEmpty()) {
                 val warningMsg = ProcessorErrors.cursorPojoMismatch(
-                    pojoTypeNames = pojoMappings.map { it.pojo.typeName.toJavaPoet() },
+                    pojoTypeNames = pojoMappings.map {
+                        it.pojo.typeName.toString(context.codeLanguage)
+                    },
                     unusedColumns = unusedColumns,
                     allColumns = columnNames,
                     pojoUnusedFields = pojoUnusedFields,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
index ec08a3d..14eba31 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
@@ -45,9 +45,8 @@
             ProcessorErrors.MISSING_RAWQUERY_ANNOTATION
         )
 
-        val returnTypeName = returnType.typeName
         context.checker.notUnbound(
-            returnTypeName, executableElement,
+            returnType, executableElement,
             ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS
         )
 
@@ -125,7 +124,9 @@
                     if (tableNames.isEmpty()) {
                         context.logger.e(
                             executableElement,
-                            ProcessorErrors.rawQueryBadEntity(it.type.typeName)
+                            ProcessorErrors.rawQueryBadEntity(
+                                it.type.asTypeName().toString(context.codeLanguage)
+                            )
                         )
                     }
                     tableNames
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
index 8d558de..631b130 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
@@ -15,7 +15,6 @@
  */
 package androidx.room.processor
 
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XAnnotationBox
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
@@ -122,7 +121,7 @@
                     context.logger.e(
                         targetEntity.element,
                         ProcessorErrors.shortcutMethodArgumentMustBeAClass(
-                            typeName = param.pojoType.typeName
+                            typeName = param.pojoType.asTypeName().toString(context.codeLanguage)
                         )
                     )
                     null
@@ -139,7 +138,7 @@
                                 context.logger.e(
                                     it.element,
                                     ProcessorErrors.cannotFindAsEntityField(
-                                        targetEntity.typeName.toJavaPoet().toString()
+                                        targetEntity.typeName.toString(context.codeLanguage)
                                     )
 
                                 )
@@ -157,7 +156,7 @@
                             context.logger.e(
                                 executableElement,
                                 ProcessorErrors.noColumnsInPartialEntity(
-                                    partialEntityName = pojo.typeName.toJavaPoet().toString()
+                                    partialEntityName = pojo.typeName.toString(context.codeLanguage)
                                 )
                             )
                         }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
index 1edbcda..1cf568e6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
@@ -16,7 +16,6 @@
 
 package androidx.room.processor
 
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.ext.isNotNone
@@ -111,7 +110,7 @@
                         Warning.INDEX_FROM_PARENT_FIELD_IS_DROPPED,
                         ProcessorErrors.droppedSuperClassFieldIndex(
                             it.columnName, element.qualifiedName,
-                            it.element.enclosingElement.className.toString()
+                            it.element.enclosingElement.asClassName().toString(context.codeLanguage)
                         )
                     )
                     null
@@ -515,7 +514,7 @@
                         Warning.INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED,
                         embedded.field.element,
                         ProcessorErrors.droppedEmbeddedIndex(
-                            entityName = embedded.pojo.typeName.toJavaPoet().toString(),
+                            entityName = embedded.pojo.typeName.toString(context.codeLanguage),
                             fieldPath = embedded.field.getPath(),
                             grandParent = element.qualifiedName
                         )
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/UpdateMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/UpdateMethodProcessor.kt
index 3b442a4..166c5eee2 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/UpdateMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/UpdateMethodProcessor.kt
@@ -18,7 +18,6 @@
 
 import androidx.room.OnConflictStrategy
 import androidx.room.Update
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
 import androidx.room.vo.UpdateMethod
@@ -52,7 +51,7 @@
                 context.checker.check(
                     missingPrimaryKeys.isEmpty(), executableElement,
                     ProcessorErrors.missingPrimaryKeysInPartialEntityForUpdate(
-                        partialEntityName = pojo.typeName.toJavaPoet().toString(),
+                        partialEntityName = pojo.typeName.toString(context.codeLanguage),
                         primaryKeyNames = missingPrimaryKeys.map { it.columnName }
                     )
                 )
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt
index 4098bb4..4b4a363 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/UpsertionMethodProcessor.kt
@@ -17,7 +17,6 @@
 package androidx.room.processor
 
 import androidx.room.Upsert
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
 import androidx.room.vo.UpsertionMethod
@@ -39,9 +38,8 @@
         )
 
         val returnType = delegate.extractReturnType()
-        val returnTypeName = returnType.typeName
         context.checker.notUnbound(
-            returnTypeName, executableElement,
+            returnType, executableElement,
             ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_UPSERTION_METHODS
         )
 
@@ -56,7 +54,7 @@
                     entity.primaryKey.autoGenerateId || !missingPrimaryKeys,
                     executableElement,
                     ProcessorErrors.missingPrimaryKeysInPartialEntityForUpsert(
-                        partialEntityName = pojo.typeName.toJavaPoet().toString(),
+                        partialEntityName = pojo.typeName.toString(context.codeLanguage),
                         primaryKeyNames = entity.primaryKey.fields.columnNames
                     )
                 )
@@ -71,7 +69,7 @@
                     missingRequiredFields.isEmpty(),
                     executableElement,
                     ProcessorErrors.missingRequiredColumnsInPartialEntity(
-                        partialEntityName = pojo.typeName.toJavaPoet().toString(),
+                        partialEntityName = pojo.typeName.toString(context.codeLanguage),
                         missingColumnNames = missingRequiredFields.map { it.columnName }
                     )
                 )
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
index 453ffd0..0aac808 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/NullAwareTypeConverterStore.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/NullAwareTypeConverterStore.kt
@@ -52,9 +52,7 @@
      */
     private val knownColumnTypes: List<XType>
 ) : TypeConverterStore {
-    private val knownColumnTypeNames = knownColumnTypes.map {
-        it.typeName
-    }
+    private val knownColumnTypeNames = knownColumnTypes.map { it.asTypeName() }
     override val typeConverters = if (context.processingEnv.backend == Backend.KSP) {
         val processedConverters = typeConverters.toMutableList()
         // create copies for converters that receive non-null values
@@ -159,7 +157,7 @@
 
     private fun isColumnType(type: XType): Boolean {
         // compare using type names to handle both null and non-null.
-        return knownColumnTypeNames.contains(type.typeName)
+        return knownColumnTypeNames.contains(type.asTypeName())
     }
 
     private fun findConverterIntoStatementInternal(
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 ff8e11b..07f549b 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
@@ -17,7 +17,6 @@
 package androidx.room.solver
 
 import androidx.annotation.VisibleForTesting
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.isArray
 import androidx.room.compiler.processing.isEnum
@@ -40,8 +39,8 @@
 import androidx.room.processor.EntityProcessor
 import androidx.room.processor.FieldProcessor
 import androidx.room.processor.PojoProcessor
+import androidx.room.processor.ProcessorErrors
 import androidx.room.processor.ProcessorErrors.DO_NOT_USE_GENERIC_IMMUTABLE_MULTIMAP
-import androidx.room.processor.ProcessorErrors.valueCollectionMustBeListOrSet
 import androidx.room.solver.binderprovider.CoroutineFlowResultBinderProvider
 import androidx.room.solver.binderprovider.CursorQueryResultBinderProvider
 import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
@@ -484,7 +483,7 @@
         if (typeMirror.isArray() && typeMirror.componentType.isNotByte()) {
             val rowAdapter =
                 findRowAdapter(typeMirror.componentType, query) ?: return null
-            return ArrayQueryResultAdapter(rowAdapter)
+            return ArrayQueryResultAdapter(typeMirror, rowAdapter)
         } else if (typeMirror.typeArguments.isEmpty()) {
             val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
             return SingleItemQueryResultAdapter(rowAdapter)
@@ -584,12 +583,12 @@
             ) ?: return null
 
             validateMapTypeArgs(
+                context = context,
                 keyTypeArg = keyTypeArg,
                 valueTypeArg = valueTypeArg,
                 keyReader = findCursorValueReader(keyTypeArg, null),
                 valueReader = findCursorValueReader(valueTypeArg, null),
-                mapInfo = mapInfo,
-                logger = context.logger
+                mapInfo = mapInfo
             )
             return GuavaImmutableMultimapQueryResultAdapter(
                 context = context,
@@ -601,9 +600,9 @@
                 immutableClassName = immutableClassName
             )
         } else if (typeMirror.isTypeOf(java.util.Map::class) ||
-            typeMirror.rawType.typeName == ARRAY_MAP.toJavaPoet() ||
-            typeMirror.rawType.typeName == LONG_SPARSE_ARRAY.toJavaPoet() ||
-            typeMirror.rawType.typeName == INT_SPARSE_ARRAY.toJavaPoet()
+            typeMirror.rawType.asTypeName().equalsIgnoreNullability(ARRAY_MAP) ||
+            typeMirror.rawType.asTypeName().equalsIgnoreNullability(LONG_SPARSE_ARRAY) ||
+            typeMirror.rawType.asTypeName().equalsIgnoreNullability(INT_SPARSE_ARRAY)
         ) {
             val mapType = when (typeMirror.rawType.asTypeName()) {
                 LONG_SPARSE_ARRAY -> MultimapQueryResultAdapter.MapType.LONG_SPARSE
@@ -650,7 +649,9 @@
                         MultimapQueryResultAdapter.CollectionValueType.SET
                     else -> {
                         context.logger.e(
-                            valueCollectionMustBeListOrSet(mapValueTypeArg.typeName)
+                            ProcessorErrors.valueCollectionMustBeListOrSet(
+                                mapValueTypeArg.asTypeName().toString(context.codeLanguage)
+                            )
                         )
                         return null
                     }
@@ -671,12 +672,12 @@
                 ) ?: return null
 
                 validateMapTypeArgs(
+                    context = context,
                     keyTypeArg = keyTypeArg,
                     valueTypeArg = valueTypeArg,
                     keyReader = findCursorValueReader(keyTypeArg, null),
                     valueReader = findCursorValueReader(valueTypeArg, null),
-                    mapInfo = mapInfo,
-                    logger = context.logger
+                    mapInfo = mapInfo
                 )
                 return MapQueryResultAdapter(
                     context = context,
@@ -701,12 +702,12 @@
                 ) ?: return null
 
                 validateMapTypeArgs(
+                    context = context,
                     keyTypeArg = keyTypeArg,
                     valueTypeArg = mapValueTypeArg,
                     keyReader = findCursorValueReader(keyTypeArg, null),
                     valueReader = findCursorValueReader(mapValueTypeArg, null),
-                    mapInfo = mapInfo,
-                    logger = context.logger
+                    mapInfo = mapInfo
                 )
                 return MapQueryResultAdapter(
                     context = context,
@@ -737,7 +738,7 @@
         }
 
         val typeElement = typeMirror.typeElement
-        if (typeElement != null && !typeMirror.typeName.isPrimitive) {
+        if (typeElement != null && !typeMirror.asTypeName().isPrimitive) {
             if (typeMirror.typeArguments.isNotEmpty()) {
                 // TODO one day support this
                 return null
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/CursorQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/CursorQueryResultBinderProvider.kt
index 009c521..cf1fdb7 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/CursorQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/CursorQueryResultBinderProvider.kt
@@ -16,9 +16,8 @@
 
 package androidx.room.solver.binderprovider
 
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XType
-import androidx.room.ext.AndroidTypeNames.CURSOR
+import androidx.room.ext.AndroidTypeNames
 import androidx.room.parser.ParsedQuery
 import androidx.room.processor.Context
 import androidx.room.solver.QueryResultBinderProvider
@@ -36,5 +35,5 @@
     }
 
     override fun matches(declared: XType): Boolean =
-        declared.typeArguments.isEmpty() && declared.typeName == CURSOR.toJavaPoet()
+        declared.typeArguments.isEmpty() && declared.asTypeName() == AndroidTypeNames.CURSOR
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
index eb300a8..f6e88f4 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/binderprovider/MultiTypedPagingSourceQueryResultBinderProvider.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.solver.binderprovider
 
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XRawType
 import androidx.room.compiler.processing.XType
 import androidx.room.parser.ParsedQuery
@@ -78,7 +79,7 @@
             return false
         }
 
-        if (declared.typeArguments.first().typeName != TypeName.INT.box()) {
+        if (declared.typeArguments.first().asTypeName() != XTypeName.BOXED_INT) {
             context.logger.e(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_TYPE)
         }
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/result/PreparedQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/result/PreparedQueryResultAdapter.kt
index f9cfbc5..7d6a09d 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/result/PreparedQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/prepared/result/PreparedQueryResultAdapter.kt
@@ -19,13 +19,13 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
 import androidx.room.compiler.codegen.XPropertySpec
-import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.isInt
 import androidx.room.compiler.processing.isKotlinUnit
 import androidx.room.compiler.processing.isLong
 import androidx.room.compiler.processing.isVoid
 import androidx.room.compiler.processing.isVoidObject
+import androidx.room.ext.KotlinTypeNames
 import androidx.room.parser.QueryType
 import androidx.room.solver.CodeGenScope
 import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
@@ -81,7 +81,7 @@
                     if (returnType.isVoidObject()) {
                         addStatement("return null")
                     } else if (returnType.isKotlinUnit() && language == CodeLanguage.JAVA) {
-                        addStatement("return %T.INSTANCE", Unit::class.asClassName())
+                        addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
                     }
                 } else {
                     val resultVar = scope.getTmpVar("_result")
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt
index cf119ed..35eb742 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/ArrayQueryResultAdapter.kt
@@ -16,45 +16,112 @@
 
 package androidx.room.solver.query.result
 
-import androidx.room.compiler.processing.isArray
-import androidx.room.ext.L
-import androidx.room.ext.T
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.codegen.box
+import androidx.room.compiler.processing.XArrayType
+import androidx.room.compiler.processing.XNullability
+import androidx.room.ext.KotlinCollectionMemberNames.ARRAY_OF_NULLS
+import androidx.room.ext.getToArrayFunction
 import androidx.room.solver.CodeGenScope
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.TypeName
 
 class ArrayQueryResultAdapter(
+    private val arrayType: XArrayType,
     private val rowAdapter: RowAdapter
 ) : QueryResultAdapter(listOf(rowAdapter)) {
-    val type = rowAdapter.out
     override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
-        scope.builder().apply {
+        scope.builder.apply {
             rowAdapter.onCursorReady(cursorVarName = cursorVarName, scope = scope)
+            val componentTypeName: XTypeName = arrayType.componentType.asTypeName()
+            val arrayTypeName = XTypeName.getArrayName(componentTypeName)
 
-            val arrayType = ArrayTypeName.of(type.typeName)
-
-            if (type.isArray()) {
-                addStatement(
-                    "final $T $L = new $T[$L.getCount()][]",
-                    arrayType, outVarName, type.componentType.typeName, cursorVarName
-                )
-            } else {
-                addStatement(
-                    "final $T $L = new $T[$L.getCount()]",
-                    arrayType, outVarName, type.typeName, cursorVarName
-                )
-            }
+            // For Java, instantiate a new array of a size using the bracket syntax, for Kotlin
+            // create the array using the std-lib function arrayOfNulls.
+            val tmpResultName = scope.getTmpVar("_tmpResult")
+            addLocalVariable(
+                name = tmpResultName,
+                typeName = XTypeName.getArrayName(componentTypeName.copy(nullable = true)),
+                assignExpr = when (language) {
+                    CodeLanguage.KOTLIN ->
+                        XCodeBlock.of(
+                            language = language,
+                            format = "%M<%T>(%L.getCount())",
+                            ARRAY_OF_NULLS,
+                            componentTypeName,
+                            cursorVarName
+                        )
+                    CodeLanguage.JAVA ->
+                        XCodeBlock.of(
+                            language = language,
+                            format = "new %T[%L.getCount()]",
+                            componentTypeName,
+                            cursorVarName
+                        )
+                }
+            )
 
             val tmpVarName = scope.getTmpVar("_item")
             val indexVar = scope.getTmpVar("_index")
-            addStatement("$T $L = 0", TypeName.INT, indexVar)
-            beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
-                addStatement("final $T $L", type.typeName, tmpVarName)
+            addLocalVariable(
+                name = indexVar,
+                typeName = XTypeName.PRIMITIVE_INT,
+                assignExpr = XCodeBlock.of(language, "0"),
+                isMutable = true
+            )
+            beginControlFlow("while (%L.moveToNext())", cursorVarName).apply {
+                addLocalVariable(
+                    name = tmpVarName,
+                    typeName = componentTypeName
+                )
                 rowAdapter.convert(tmpVarName, cursorVarName, scope)
-                addStatement("$L[$L] = $L", outVarName, indexVar, tmpVarName)
-                addStatement("$L ++", indexVar)
+                addStatement("%L[%L] = %L", tmpResultName, indexVar, tmpVarName)
+                addStatement("%L++", indexVar)
             }
             endControlFlow()
+
+            // Finally initialize _result to be returned. Will avoid an unnecessary cast in
+            // Kotlin if the Entity was already nullable.
+            val assignCode = XCodeBlock.of(
+                language = language,
+                format = "%L",
+                tmpResultName
+            ).let {
+                if (
+                    language == CodeLanguage.KOTLIN &&
+                    componentTypeName.nullability == XNullability.NONNULL
+                ) {
+                    XCodeBlock.ofCast(
+                        language = language,
+                        typeName = XTypeName.getArrayName(componentTypeName.box()),
+                        expressionBlock = it
+                    )
+                } else {
+                    it
+                }
+            }.let {
+                // If the component is a primitive type and the language is Kotlin, we need to use
+                // an additional built-in function to cast from the boxed to the primitive array
+                // type, i.e. Array<Int> to IntArray.
+                if (
+                    language == CodeLanguage.KOTLIN &&
+                    componentTypeName.isPrimitive
+                ) {
+                    XCodeBlock.of(
+                        language = language,
+                        format = "(%L).%L",
+                        it,
+                        getToArrayFunction(componentTypeName)
+                    )
+                } else {
+                    it
+                }
+            }
+            addLocalVariable(
+                name = outVarName,
+                typeName = arrayTypeName,
+                assignExpr = assignCode
+            )
         }
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultimapQueryResultAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultimapQueryResultAdapter.kt
index 41f6df3..810202e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultimapQueryResultAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/MultimapQueryResultAdapter.kt
@@ -19,12 +19,10 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.CollectionTypeNames
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.implementsEqualsAndHashcode
-import androidx.room.log.RLog
 import androidx.room.parser.ParsedQuery
 import androidx.room.processor.Context
 import androidx.room.processor.ProcessorErrors
@@ -79,14 +77,18 @@
                     is SingleNamedColumnRowAdapter.SingleNamedColumnRowMapping ->
                         MAP_INFO to null
                     is PojoRowAdapter.PojoMapping ->
-                        POJO to it.pojo.typeName.toJavaPoet()
+                        POJO to it.pojo.typeName
                     is EntityRowAdapter.EntityMapping ->
-                        ENTITY to it.entity.typeName.toJavaPoet()
+                        ENTITY to it.entity.typeName
                     else -> error("Unknown mapping type: $it")
                 }
                 context.logger.w(
                     Warning.AMBIGUOUS_COLUMN_IN_RESULT,
-                    ProcessorErrors.ambiguousColumn(ambiguousColumnName, location, objectTypeName)
+                    ProcessorErrors.ambiguousColumn(
+                        columnName = ambiguousColumnName,
+                        location = location,
+                        typeName = objectTypeName?.toString(context.codeLanguage)
+                    )
                 )
             }
         }
@@ -115,37 +117,37 @@
          * of a Dao method.
          */
         fun validateMapTypeArgs(
+            context: Context,
             keyTypeArg: XType,
             valueTypeArg: XType,
             keyReader: CursorValueReader?,
             valueReader: CursorValueReader?,
             mapInfo: MapInfo?,
-            logger: RLog
         ) {
 
             if (!keyTypeArg.implementsEqualsAndHashcode()) {
-                logger.w(
+                context.logger.w(
                     Warning.DOES_NOT_IMPLEMENT_EQUALS_HASHCODE,
                     ProcessorErrors.classMustImplementEqualsAndHashCode(
-                        keyTypeArg.typeName.toString()
+                        keyTypeArg.asTypeName().toString(context.codeLanguage)
                     )
                 )
             }
 
             val hasKeyColumnName = mapInfo?.keyColumnName?.isNotEmpty() ?: false
             if (!hasKeyColumnName && keyReader != null) {
-                logger.e(
+                context.logger.e(
                     ProcessorErrors.keyMayNeedMapInfo(
-                        keyTypeArg.typeName
+                        keyTypeArg.asTypeName().toString(context.codeLanguage)
                     )
                 )
             }
 
             val hasValueColumnName = mapInfo?.valueColumnName?.isNotEmpty() ?: false
             if (!hasValueColumnName && valueReader != null) {
-                logger.e(
+                context.logger.e(
                     ProcessorErrors.valueMayNeedMapInfo(
-                        valueTypeArg.typeName
+                        valueTypeArg.asTypeName().toString(context.codeLanguage)
                     )
                 )
             }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
index d2b4ba1..570c1f9 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
@@ -16,7 +16,6 @@
 
 package androidx.room.solver.query.result
 
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XType
 import androidx.room.parser.ParsedQuery
 import androidx.room.processor.Context
@@ -69,14 +68,18 @@
             if (nonNulls.isNotEmpty()) {
                 context.logger.e(
                     ProcessorErrors.pojoMissingNonNull(
-                        pojoTypeName = pojo.typeName.toJavaPoet(),
+                        pojoTypeName = pojo.typeName.toString(context.codeLanguage),
                         missingPojoFields = nonNulls.map { it.name },
                         allQueryColumns = info.columns.map { it.name }
                     )
                 )
             }
             if (matchedFields.isEmpty()) {
-                context.logger.e(ProcessorErrors.cannotFindQueryResultAdapter(out.typeName))
+                context.logger.e(
+                    ProcessorErrors.cannotFindQueryResultAdapter(
+                        out.asTypeName().toString(context.codeLanguage)
+                    )
+                )
             }
         } else {
             matchedFields = remainingFields.map { it }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt
index 56d437e..682c533 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/shortcut/result/InsertOrUpsertMethodAdapter.kt
@@ -20,18 +20,19 @@
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeName
-import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.isArray
 import androidx.room.compiler.processing.isKotlinUnit
 import androidx.room.compiler.processing.isLong
 import androidx.room.compiler.processing.isVoid
 import androidx.room.compiler.processing.isVoidObject
+import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.KotlinTypeNames
+import androidx.room.ext.isList
 import androidx.room.processor.Context
 import androidx.room.processor.ProcessorErrors
 import androidx.room.solver.CodeGenScope
 import androidx.room.vo.ShortcutQueryParameter
-import com.squareup.javapoet.TypeName
 
 class InsertOrUpsertMethodAdapter private constructor(private val methodType: MethodType) {
     companion object {
@@ -142,7 +143,7 @@
             } else if (returnType.isArray()) {
                 val param = returnType.componentType
                 if (param.isLong()) {
-                    if (param.typeName == TypeName.LONG) {
+                    if (param.asTypeName() == XTypeName.PRIMITIVE_LONG) {
                         ReturnType.ID_ARRAY
                     } else {
                         ReturnType.ID_ARRAY_BOX
@@ -215,7 +216,7 @@
                 } else if (methodReturnType == ReturnType.VOID_OBJECT) {
                     addStatement("return null")
                 } else if (methodReturnType == ReturnType.UNIT && language == CodeLanguage.JAVA) {
-                    addStatement("return %T.INSTANCE", Unit::class.asClassName())
+                    addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
                 }
             }
             nextControlFlow("finally").apply {
@@ -244,7 +245,7 @@
         val returnTypeName: XTypeName
     ) {
         VOID("", XTypeName.UNIT_VOID), // return void
-        VOID_OBJECT("", Void::class.asClassName()), // return Void
+        VOID_OBJECT("", CommonTypeNames.VOID), // return Void
         UNIT("", XTypeName.UNIT_VOID), // return kotlin.Unit.INSTANCE
         SINGLE_ID("AndReturnId", XTypeName.PRIMITIVE_LONG), // return long
         ID_ARRAY(
@@ -253,11 +254,11 @@
         ), // return long[]
         ID_ARRAY_BOX(
             "AndReturnIdsArrayBox",
-            XTypeName.getArrayName(Long::class.asClassName())
+            XTypeName.getArrayName(XTypeName.BOXED_LONG)
         ), // return Long[]
         ID_LIST(
             "AndReturnIdsList",
-            List::class.asClassName().parametrizedBy(Long::class.asClassName())
+            CommonTypeNames.LIST.parametrizedBy(XTypeName.BOXED_LONG)
         ), // return List<Long>
     }
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/CoroutineTransactionMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/CoroutineTransactionMethodBinder.kt
index 828d0db..46c348e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/CoroutineTransactionMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/CoroutineTransactionMethodBinder.kt
@@ -22,7 +22,6 @@
 import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeName
-import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.Function1TypeSpec
 import androidx.room.ext.KotlinTypeNames
@@ -87,7 +86,7 @@
                     XTypeName.getConsumerSuperName(returnType.asTypeName())
                 ),
                 parameterName = innerContinuationParamName,
-                returnTypeName = Any::class.asClassName()
+                returnTypeName = KotlinTypeNames.ANY
             ) {
                 addStatement("%L", adapterScope.generate())
             }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt
index 6510c5b..4a8cae4 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/transaction/binder/InstantTransactionMethodBinder.kt
@@ -19,9 +19,9 @@
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XPropertySpec
-import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.isKotlinUnit
+import androidx.room.ext.KotlinTypeNames
 import androidx.room.ext.isNotKotlinUnit
 import androidx.room.ext.isNotVoid
 import androidx.room.solver.CodeGenScope
@@ -71,7 +71,7 @@
                 if (resultVar != null) {
                     addStatement("return %N", resultVar)
                 } else if (returnType.isKotlinUnit() && language == CodeLanguage.JAVA) {
-                    addStatement("return %T.INSTANCE", Unit::class.asClassName())
+                    addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
                 }
             }
             nextControlFlow("finally").apply {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/BoxedBooleanToBoxedIntConverter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/BoxedBooleanToBoxedIntConverter.kt
index 92acbc1..4d2d68f0 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/BoxedBooleanToBoxedIntConverter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/BoxedBooleanToBoxedIntConverter.kt
@@ -18,7 +18,7 @@
 
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.asClassName
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XProcessingEnv
 import androidx.room.solver.CodeGenScope
 
@@ -28,10 +28,10 @@
 object BoxedBooleanToBoxedIntConverter {
     fun create(processingEnvironment: XProcessingEnv): List<TypeConverter> {
         val tBoolean = processingEnvironment.requireType(
-            Boolean::class.asClassName()
+            XTypeName.BOXED_BOOLEAN
         ).makeNullable()
         val tInt = processingEnvironment.requireType(
-            Int::class.asClassName()
+            XTypeName.BOXED_INT
         ).makeNullable()
         return listOf(
             object : SingleStatementTypeConverter(tBoolean, tInt) {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
index ac06bb2..9905c5e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
@@ -23,6 +23,7 @@
 import androidx.room.compiler.processing.XEnumTypeElement
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
+import androidx.room.ext.CommonTypeNames
 import androidx.room.parser.SQLTypeAffinity.TEXT
 import androidx.room.solver.CodeGenScope
 import androidx.room.writer.TypeWriter
@@ -139,7 +140,7 @@
                     }
                 }.build()
                 builder.apply {
-                    returns(String::class.asClassName().copy(nullable = false))
+                    returns(CommonTypeNames.STRING.copy(nullable = false))
                     addParameter(
                         out.asTypeName().copy(nullable = false),
                         paramName
@@ -207,7 +208,7 @@
                 builder.apply {
                     returns(out.asTypeName().copy(nullable = false))
                     addParameter(
-                        String::class.asClassName().copy(nullable = false),
+                        CommonTypeNames.STRING.copy(nullable = false),
                         paramName
                     )
                     addCode(body)
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
index 3b30e79..815fe8d 100644
--- 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
@@ -92,7 +92,8 @@
     }
 
     private fun XCodeBlock.Builder.addIllegalStateException() {
-        val message = "Expected non-null ${from.typeName}, but it was null."
+        val typeName = from.asTypeName().copy(nullable = false).toString(language)
+        val message = "Expected non-null $typeName, but it was null."
         when (language) {
             CodeLanguage.JAVA -> {
                 addStatement(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/StringColumnTypeAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/StringColumnTypeAdapter.kt
index d8bb5d3..c68a9a9 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/types/StringColumnTypeAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/types/StringColumnTypeAdapter.kt
@@ -16,7 +16,6 @@
 
 package androidx.room.solver.types
 
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XProcessingEnv
 import androidx.room.compiler.processing.XType
@@ -69,7 +68,7 @@
 
     companion object {
         fun create(env: XProcessingEnv): List<StringColumnTypeAdapter> {
-            val stringType = env.requireType(CommonTypeNames.STRING.toJavaPoet())
+            val stringType = env.requireType(CommonTypeNames.STRING)
             return if (env.backend == XProcessingEnv.Backend.KSP) {
                 listOf(
                     StringColumnTypeAdapter(stringType.makeNonNullable()),
@@ -77,7 +76,7 @@
                 )
             } else {
                 listOf(
-                    StringColumnTypeAdapter(stringType)
+                    StringColumnTypeAdapter(stringType.makeNullable())
                 )
             }
         }
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
index 2465b26..e478031 100644
--- 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
@@ -40,7 +40,7 @@
         // 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) {
+        return if (to.asTypeName().isBoxedPrimitive && from.asTypeName().isPrimitive) {
             super.doConvert(inputVarName, scope)
         } else {
             inputVarName
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/Field.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/Field.kt
index bf23f75..7921989 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/Field.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/Field.kt
@@ -17,7 +17,6 @@
 package androidx.room.vo
 
 import androidx.room.compiler.codegen.XTypeName
-import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.XFieldElement
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
@@ -103,7 +102,7 @@
 
             if (
                 typeName == XTypeName.PRIMITIVE_BOOLEAN ||
-                typeName == Boolean::class.asClassName()
+                typeName == XTypeName.BOXED_BOOLEAN
             ) {
                 if (name.length > 2 && name.startsWith("is") && name[2].isUpperCase()) {
                     result.add(name.substring(2).decapitalize(Locale.US))
@@ -120,7 +119,7 @@
         nameWithVariations.map { "get${it.capitalize(Locale.US)}" } +
             if (
                 typeName == XTypeName.PRIMITIVE_BOOLEAN ||
-                typeName == Boolean::class.asClassName()
+                typeName == XTypeName.BOXED_BOOLEAN
             ) {
                 nameWithVariations.flatMap {
                     listOf("is${it.capitalize(Locale.US)}", "has${it.capitalize(Locale.US)}")
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt
index 3aafee5..171aec5 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/RawQueryMethod.kt
@@ -17,10 +17,10 @@
 package androidx.room.vo
 
 import androidx.room.compiler.codegen.XTypeName
-import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.isKotlinUnit
+import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.ext.isNotVoid
 import androidx.room.solver.query.result.QueryResultBinder
@@ -46,7 +46,7 @@
         val paramName: String,
         val typeName: XTypeName
     ) {
-        fun isString() = String::class.asClassName() == typeName
+        fun isString() = CommonTypeNames.STRING == typeName
         fun isSupportQuery() = SupportDbTypeNames.QUERY == typeName
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
index 219cb0d..dce1f83 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
@@ -21,7 +21,6 @@
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
 import androidx.room.compiler.codegen.XTypeName
-import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.XNullability
 import androidx.room.ext.CollectionTypeNames
 import androidx.room.ext.CommonTypeNames
@@ -31,8 +30,8 @@
 import androidx.room.parser.SQLTypeAffinity
 import androidx.room.parser.SqlParser
 import androidx.room.processor.Context
+import androidx.room.processor.ProcessorErrors
 import androidx.room.processor.ProcessorErrors.ISSUE_TRACKER_LINK
-import androidx.room.processor.ProcessorErrors.cannotFindQueryResultAdapter
 import androidx.room.processor.ProcessorErrors.relationAffinityMismatch
 import androidx.room.processor.ProcessorErrors.relationJunctionChildAffinityMismatch
 import androidx.room.processor.ProcessorErrors.relationJunctionParentAffinityMismatch
@@ -44,7 +43,6 @@
 import androidx.room.verifier.DatabaseVerificationErrors
 import androidx.room.writer.QueryWriter
 import androidx.room.writer.RelationCollectorFunctionWriter
-import java.nio.ByteBuffer
 import java.util.Locale
 
 /**
@@ -413,7 +411,9 @@
                 if (rowAdapter == null) {
                     context.logger.e(
                         relation.field.element,
-                        cannotFindQueryResultAdapter(relation.pojoType.typeName)
+                        ProcessorErrors.cannotFindQueryResultAdapter(
+                            relation.pojoType.asTypeName().toString(context.codeLanguage)
+                        )
                     )
                     null
                 } else {
@@ -544,14 +544,14 @@
                     if (canUseLongSparseArray) {
                         XTypeName.PRIMITIVE_LONG
                     } else {
-                        Long::class.asClassName()
+                        XTypeName.BOXED_LONG
                     }
-                SQLTypeAffinity.REAL -> Double::class.asClassName()
-                SQLTypeAffinity.TEXT -> String::class.asClassName()
-                SQLTypeAffinity.BLOB -> ByteBuffer::class.asClassName()
+                SQLTypeAffinity.REAL -> XTypeName.BOXED_DOUBLE
+                SQLTypeAffinity.TEXT -> CommonTypeNames.STRING
+                SQLTypeAffinity.BLOB -> CommonTypeNames.BYTE_BUFFER
                 else -> {
                     // no affinity default to String
-                    String::class.asClassName()
+                    CommonTypeNames.STRING
                 }
             }
         }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
index ea0d93b..17a4ce7 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
@@ -31,6 +31,7 @@
 import androidx.room.compiler.processing.XElement
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
+import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomMemberNames
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.RoomTypeNames.DELETE_OR_UPDATE_ADAPTER
@@ -227,8 +228,8 @@
             },
         ).apply {
             returns(
-                List::class.asClassName().parametrizedBy(
-                    Class::class.asClassName().parametrizedBy(XTypeName.ANY_WILDCARD)
+                CommonTypeNames.LIST.parametrizedBy(
+                    CommonTypeNames.JAVA_CLASS.parametrizedBy(XTypeName.ANY_WILDCARD)
                 )
             )
             addCode(body)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
index f1d09cf..37941e3 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
@@ -26,7 +26,6 @@
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.codegen.XTypeSpec.Builder.Companion.addOriginatingElement
-import androidx.room.compiler.codegen.asClassName
 import androidx.room.ext.AndroidTypeNames
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.KotlinTypeNames
@@ -67,7 +66,7 @@
     private fun createCreateTypeConvertersMap(): XFunSpec {
         val scope = CodeGenScope(this)
         val classOfAnyTypeName = CommonTypeNames.JAVA_CLASS.parametrizedBy(
-            XTypeName.getProducerExtendsName(Any::class.asClassName())
+            XTypeName.getProducerExtendsName(KotlinTypeNames.ANY)
         )
         val typeConvertersTypeName = CommonTypeNames.HASH_MAP.parametrizedBy(
             classOfAnyTypeName,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt
index 399a208..c1f0457 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityCursorConverterWriter.kt
@@ -16,10 +16,10 @@
 
 package androidx.room.writer
 
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XFunSpec
 import androidx.room.compiler.codegen.XTypeName
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.ext.AndroidTypeNames.CURSOR
 import androidx.room.ext.RoomMemberNames
 import androidx.room.ext.capitalize
@@ -30,7 +30,7 @@
 import java.util.Locale
 
 class EntityCursorConverterWriter(val entity: Entity) : TypeWriter.SharedFunctionSpec(
-    "entityCursorConverter_${entity.typeName.toJavaPoet().toString().stripNonJava()}"
+    "entityCursorConverter_${entity.typeName.toString(CodeLanguage.JAVA).stripNonJava()}"
 ) {
     override fun getUniqueKey(): String {
         return "generic_entity_converter_of_${entity.element.qualifiedName}"
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeletionAdapterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeletionAdapterWriter.kt
index aeb8f5b2..d1d1379 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeletionAdapterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityDeletionAdapterWriter.kt
@@ -21,7 +21,7 @@
 import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.codegen.asClassName
+import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.solver.CodeGenScope
@@ -63,7 +63,7 @@
                     visibility = VisibilityModifier.PUBLIC,
                     isOverride = true
                 ).apply {
-                    returns(String::class.asClassName())
+                    returns(CommonTypeNames.STRING)
                     val query = "DELETE FROM `$tableName` WHERE " +
                         fields.columnNames.joinToString(" AND ") { "`$it` = ?" }
                     addStatement("return %S", query)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt
index adaf080..5e2b01f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityInsertionAdapterWriter.kt
@@ -22,8 +22,8 @@
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.XNullability
+import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.solver.CodeGenScope
@@ -78,7 +78,7 @@
                     visibility = VisibilityModifier.PUBLIC,
                     isOverride = true
                 ).apply {
-                    returns(String::class.asClassName())
+                    returns(CommonTypeNames.STRING)
                     val query = buildString {
                         if (onConflict.isNotEmpty()) {
                             append("INSERT OR $onConflict INTO `$tableName`")
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt
index fc17282..c7f487c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/EntityUpdateAdapterWriter.kt
@@ -20,7 +20,7 @@
 import androidx.room.compiler.codegen.XFunSpec
 import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.codegen.asClassName
+import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.SupportDbTypeNames
 import androidx.room.solver.CodeGenScope
@@ -56,7 +56,7 @@
                     visibility = VisibilityModifier.PUBLIC,
                     isOverride = true
                 ).apply {
-                    returns(String::class.asClassName())
+                    returns(CommonTypeNames.STRING)
                     val pojoCols = pojo.columnNames.joinToString(",") {
                         "`$it` = ?"
                     }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/PreparedStatementWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/PreparedStatementWriter.kt
index 4fde443..1e742cf 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/PreparedStatementWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/PreparedStatementWriter.kt
@@ -20,7 +20,7 @@
 import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XPropertySpec
 import androidx.room.compiler.codegen.XTypeSpec
-import androidx.room.compiler.codegen.asClassName
+import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomTypeNames
 import androidx.room.solver.CodeGenScope
 
@@ -39,7 +39,7 @@
                     visibility = VisibilityModifier.PUBLIC,
                     isOverride = true
                 ).apply {
-                    returns(String::class.asClassName())
+                    returns(CommonTypeNames.STRING)
                     val queryName = scope.getTmpVar("_query")
                     val queryGenScope = scope.fork()
                     queryWriter.prepareQuery(queryName, queryGenScope)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt
index ec8031f..c170531 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/QueryWriter.kt
@@ -21,6 +21,7 @@
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.asClassName
+import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.RoomMemberNames
 import androidx.room.ext.RoomTypeNames
 import androidx.room.parser.ParsedQuery
@@ -112,7 +113,7 @@
                 }
                 addLocalVal(
                     outSqlQueryName,
-                    String::class.asClassName(),
+                    CommonTypeNames.STRING,
                     "%L.toString()",
                     stringBuilderVar
                 )
@@ -140,7 +141,7 @@
             } else {
                 addLocalVal(
                     outSqlQueryName,
-                    String::class.asClassName(),
+                    CommonTypeNames.STRING,
                     "%S",
                     query.queryWithReplacedBindParams
                 )
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/RelationCollectorFunctionWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/RelationCollectorFunctionWriter.kt
index b76318d..1ecfd81 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/RelationCollectorFunctionWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/RelationCollectorFunctionWriter.kt
@@ -23,13 +23,12 @@
 import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
 import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XTypeName
-import androidx.room.compiler.codegen.asClassName
-import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.ext.AndroidTypeNames
 import androidx.room.ext.CollectionTypeNames
 import androidx.room.ext.CollectionsSizeExprCode
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.Function1TypeSpec
+import androidx.room.ext.KotlinTypeNames
 import androidx.room.ext.MapKeySetExprCode
 import androidx.room.ext.RoomMemberNames
 import androidx.room.ext.RoomTypeNames
@@ -45,7 +44,7 @@
     private val collector: RelationCollector
 ) : TypeWriter.SharedFunctionSpec(
     "fetchRelationship${collector.relation.entity.tableName.stripNonJava()}" +
-        "As${collector.relation.pojoTypeName.toJavaPoet().toString().stripNonJava()}"
+        "As${collector.relation.pojoTypeName.toString(CodeLanguage.JAVA).stripNonJava()}"
 ) {
     companion object {
         const val PARAM_MAP_VARIABLE = "_map"
@@ -61,7 +60,7 @@
         val relation = collector.relation
         return "RelationCollectorMethodWriter" +
             "-${collector.mapTypeName}" +
-            "-${relation.entity.typeName.toJavaPoet()}" +
+            "-${relation.entity.typeName.toString(CodeLanguage.JAVA)}" +
             "-${relation.entityField.columnName}" +
             "-${relation.pojoTypeName}" +
             "-${relation.createLoadAllSql()}"
@@ -238,7 +237,7 @@
                     )
                     indent()
                     addStatement("%L", getRecursiveCall(paramName))
-                    addStatement("return %T.INSTANCE", Unit::class.asClassName())
+                    addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
                     unindent()
                     addStatement("})")
                 } else {
@@ -246,10 +245,10 @@
                         language = language,
                         parameterTypeName = collector.mapTypeName,
                         parameterName = paramName,
-                        returnTypeName = Unit::class.asClassName(),
+                        returnTypeName = KotlinTypeNames.UNIT,
                     ) {
                         addStatement("%L", getRecursiveCall(paramName))
-                        addStatement("return %T.INSTANCE", Unit::class.asClassName())
+                        addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
                     }
                     addStatement(
                         "%M(%L, %L, %L)",
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
index f49c5c2..7249aa7 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/TypeWriter.kt
@@ -29,8 +29,9 @@
 import androidx.room.ext.S
 import androidx.room.solver.CodeGenScope
 import com.squareup.kotlinpoet.javapoet.JAnnotationSpec
+import com.squareup.kotlinpoet.javapoet.JClassName
 import com.squareup.kotlinpoet.javapoet.KAnnotationSpec
-import com.squareup.kotlinpoet.javapoet.toKClassName
+import com.squareup.kotlinpoet.javapoet.KClassName
 import kotlin.reflect.KClass
 
 /**
@@ -77,19 +78,18 @@
     }
 
     private fun addSuppressWarnings(builder: XTypeSpec.Builder) {
-        val suppressionKeys = arrayOf("unchecked", "deprecation")
         builder.apply(
             javaTypeBuilder = {
                 addAnnotation(
                     com.squareup.javapoet.AnnotationSpec.builder(SuppressWarnings::class.java)
-                        .addMember("value", "{$S, $S}", *suppressionKeys)
+                        .addMember("value", "{$S, $S}", "unchecked", "deprecation")
                         .build()
                 )
             },
             kotlinTypeBuilder = {
                 addAnnotation(
                     com.squareup.kotlinpoet.AnnotationSpec.builder(Suppress::class)
-                        .addMember("names = [%S, %S]", *suppressionKeys)
+                        .addMember("names = [%S, %S]", "UNCHECKED_CAST", "DEPRECATION")
                         .build()
                 )
             }
@@ -101,18 +101,19 @@
         processingEnv: XProcessingEnv
     ) {
         processingEnv.findGeneratedAnnotation()?.let {
+            val annotationName = it.asClassName().canonicalName
             val memberValue = RoomProcessor::class.java.canonicalName
             adapterTypeSpecBuilder.apply(
                 javaTypeBuilder = {
                     addAnnotation(
-                        JAnnotationSpec.builder(it.className)
+                        JAnnotationSpec.builder(JClassName.bestGuess(annotationName))
                             .addMember("value", "$S", memberValue)
                             .build()
                     )
                 },
                 kotlinTypeBuilder = {
                     addAnnotation(
-                        KAnnotationSpec.builder(it.className.toKClassName())
+                        KAnnotationSpec.builder(KClassName.bestGuess(annotationName))
                             .addMember("value = [%S]", memberValue)
                             .build()
                     )
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
index afac914..78e96b6 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/ext/ElementExtTest.kt
@@ -16,14 +16,14 @@
 
 package androidx.room.ext
 
+import androidx.room.compiler.codegen.XClassName
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.runProcessorTest
 import com.google.common.truth.Truth.assertThat
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -210,34 +210,9 @@
                 .first {
                     it.jvmName == "method"
                 }
-            assertThat(field.type.typeName).isEqualTo(TypeName.INT)
-            assertThat(method.returnType.typeName).isEqualTo(TypeName.INT)
-            assertThat(element.type.typeName).isEqualTo(ClassName.get("foo.bar", "Baz"))
-        }
-    }
-
-    @Test
-    fun primitiveTypes() {
-        // check that we can also find primitive types from the common API
-        val primitiveTypeNames = listOf(
-            TypeName.BOOLEAN,
-            TypeName.BYTE,
-            TypeName.SHORT,
-            TypeName.INT,
-            TypeName.LONG,
-            TypeName.CHAR,
-            TypeName.FLOAT,
-            TypeName.DOUBLE
-        )
-        runTest { invocation ->
-            val processingEnv = invocation.processingEnv
-            primitiveTypeNames.forEach { primitiveTypeName ->
-                val typeMirror = processingEnv.requireType(primitiveTypeName)
-                assertThat(typeMirror.typeName).isEqualTo(primitiveTypeName)
-                assertThat(
-                    typeMirror.boxed().typeName
-                ).isEqualTo(primitiveTypeName.box())
-            }
+            assertThat(field.type.asTypeName()).isEqualTo(XTypeName.PRIMITIVE_INT)
+            assertThat(method.returnType.asTypeName()).isEqualTo(XTypeName.PRIMITIVE_INT)
+            assertThat(element.type.asTypeName()).isEqualTo(XClassName.get("foo.bar", "Baz"))
         }
     }
 
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
index fff0299..e9ad56d 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/parser/SQLTypeAffinityTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.parser
 
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.util.runProcessorTest
@@ -45,7 +46,7 @@
             }
 
             fun XType.toSignature(): String {
-                return "${typeName}${nullability.toSignature()}"
+                return "${asTypeName().toString(CodeLanguage.JAVA)}${nullability.toSignature()}"
             }
 
             val result = SQLTypeAffinity.values().associate {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
index e328370..5197e66 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
@@ -131,7 +131,7 @@
             ).process()
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    ProcessorErrors.autoMigrationElementMustImplementSpec("MyAutoMigration")
+                    ProcessorErrors.autoMigrationElementMustImplementSpec("foo.bar.MyAutoMigration")
                 )
             }
         }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
index 5ef999b..aa449e9 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
@@ -338,7 +338,7 @@
                     // instead of not running the code path in ksp tests
                     hasErrorContaining(TYPE_CONVERTER_EMPTY_CLASS)
                 } else {
-                    hasErrorContaining(ProcessorErrors.typeConverterMustBeDeclared(TypeName.INT))
+                    hasErrorContaining(ProcessorErrors.typeConverterMustBeDeclared("int"))
                 }
             }
         }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
index 52f0496..78f4cec 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
@@ -29,7 +29,6 @@
 import androidx.room.vo.ReadQueryMethod
 import androidx.room.vo.Warning
 import com.google.common.truth.Truth
-import com.squareup.javapoet.TypeName
 import createVerifierFromEntitiesAndViews
 import java.io.File
 import org.hamcrest.CoreMatchers.`is`
@@ -438,7 +437,7 @@
             assertThat(method.element.jvmName, `is`("getAllIds"))
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    ProcessorErrors.cannotFindQueryResultAdapter(TypeName.VOID)
+                    ProcessorErrors.cannotFindQueryResultAdapter("void")
                 )
             }
         }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index f6f9d51..07063d5 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -18,6 +18,7 @@
 
 import COMMON
 import androidx.room.DatabaseProcessingStep
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.CompilationResultSubject
@@ -39,12 +40,14 @@
 import androidx.room.vo.Warning
 import com.google.auto.service.processor.AutoServiceProcessor
 import com.google.common.truth.Truth.assertThat
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
-import org.hamcrest.CoreMatchers.`is`
+import java.io.File
+import java.io.FileOutputStream
+import java.net.URL
+import java.net.URLClassLoader
 import org.hamcrest.CoreMatchers.equalTo
 import org.hamcrest.CoreMatchers.hasItems
 import org.hamcrest.CoreMatchers.instanceOf
+import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.CoreMatchers.not
 import org.hamcrest.CoreMatchers.notNullValue
 import org.hamcrest.CoreMatchers.sameInstance
@@ -55,10 +58,6 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.Mockito.mock
-import java.io.File
-import java.io.FileOutputStream
-import java.net.URL
-import java.net.URLClassLoader
 
 @RunWith(JUnit4::class)
 class DatabaseProcessorTest {
@@ -253,7 +252,7 @@
             assertThat(db.entities.size, `is`(2))
             assertThat(db.daoMethods.map { it.element.jvmName }, `is`(listOf("userDao", "bookDao")))
             assertThat(
-                db.entities.map { it.type.typeName.toString() },
+                db.entities.map { it.type.asTypeName().toString(CodeLanguage.JAVA) },
                 `is`(listOf("foo.bar.User", "foo.bar.Book"))
             )
         }
@@ -512,7 +511,7 @@
                 )
                 hasErrorContaining(
                     ProcessorErrors.duplicateDao(
-                        ClassName.get("foo.bar", "UserDao"), listOf("userDao", "userDao2")
+                        "foo.bar.UserDao", listOf("userDao", "userDao2")
                     )
                 )
             }
@@ -594,7 +593,8 @@
             """
                 package foo.bar;
                 import androidx.room.*;
-                @Entity(foreignKeys = @ForeignKey(entity = ${COMMON.USER_TYPE_NAME}.class,
+                @Entity(foreignKeys = @ForeignKey(
+                        entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                         parentColumns = "lastName",
                         childColumns = "name"))
                 public class Entity1 {
@@ -630,7 +630,8 @@
             """
                 package foo.bar;
                 import androidx.room.*;
-                @Entity(foreignKeys = @ForeignKey(entity = ${COMMON.USER_TYPE_NAME}.class,
+                @Entity(foreignKeys = @ForeignKey(
+                        entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                         parentColumns = "lastName",
                         childColumns = "name"))
                 public class Entity1 {
@@ -651,7 +652,7 @@
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     ProcessorErrors.foreignKeyMissingIndexInParent(
-                        parentEntity = COMMON.USER_TYPE_NAME.toString(),
+                        parentEntity = COMMON.USER_TYPE_NAME.canonicalName,
                         parentColumns = listOf("lastName"),
                         childEntity = "foo.bar.Entity1",
                         childColumns = listOf("name")
@@ -1247,14 +1248,10 @@
             } else {
                 invocation.assertCompilationResult {
                     hasErrorContaining(
-                        ProcessorErrors.invalidEntityTypeInDatabaseAnnotation(
-                            TypeName.LONG
-                        )
+                        ProcessorErrors.invalidEntityTypeInDatabaseAnnotation("long")
                     )
                     hasErrorContaining(
-                        ProcessorErrors.invalidViewTypeInDatabaseAnnotation(
-                            TypeName.INT
-                        )
+                        ProcessorErrors.invalidViewTypeInDatabaseAnnotation("int")
                     )
                 }
             }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
index 6c0353f..089cd3f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
@@ -18,16 +18,17 @@
 
 import COMMON
 import androidx.room.Dao
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XTypeName
-import androidx.room.compiler.codegen.toJavaPoet
+import androidx.room.compiler.codegen.asMutableClassName
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runProcessorTest
-import androidx.room.ext.CommonTypeNames.STRING
+import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.GuavaUtilConcurrentTypeNames
 import androidx.room.ext.KotlinTypeNames
 import androidx.room.ext.LifecyclesTypeNames
@@ -36,10 +37,6 @@
 import androidx.room.ext.RxJava3TypeNames
 import androidx.room.testing.context
 import androidx.room.vo.DeleteOrUpdateShortcutMethod
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
 import kotlin.reflect.KClass
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
@@ -75,7 +72,7 @@
                 """
 
         const val DAO_SUFFIX = "}"
-        val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
+        val USER_TYPE_NAME: XTypeName = COMMON.USER_TYPE_NAME
         val USERNAME_TYPE_NAME: XTypeName = XClassName.get("foo.bar", "Username")
         val BOOK_TYPE_NAME: XTypeName = XClassName.get("foo.bar", "Book")
     }
@@ -109,12 +106,12 @@
             assertThat(shortcut.element.jvmName, `is`("foo"))
             assertThat(shortcut.parameters.size, `is`(1))
             val param = shortcut.parameters.first()
-            assertThat(param.type.typeName, `is`(USER_TYPE_NAME))
-            assertThat(param.pojoType?.typeName, `is`(USER_TYPE_NAME))
+            assertThat(param.type.asTypeName(), `is`(USER_TYPE_NAME))
+            assertThat(param.pojoType?.asTypeName(), `is`(USER_TYPE_NAME))
             assertThat(shortcut.entities.size, `is`(1))
             assertThat(shortcut.entities["user"]?.isPartialEntity, `is`(false))
             assertThat(
-                shortcut.entities["user"]?.pojo?.typeName?.toJavaPoet(),
+                shortcut.entities["user"]?.pojo?.typeName,
                 `is`(USER_TYPE_NAME)
             )
         }
@@ -151,16 +148,16 @@
 
             assertThat(shortcut.parameters.size, `is`(2))
             shortcut.parameters.forEach {
-                assertThat(it.type.typeName, `is`(USER_TYPE_NAME))
-                assertThat(it.pojoType?.typeName, `is`(USER_TYPE_NAME))
+                assertThat(it.type.asTypeName(), `is`(USER_TYPE_NAME))
+                assertThat(it.pojoType?.asTypeName(), `is`(USER_TYPE_NAME))
             }
             assertThat(shortcut.entities.size, `is`(2))
             assertThat(
-                shortcut.entities["u1"]?.pojo?.typeName?.toJavaPoet(),
+                shortcut.entities["u1"]?.pojo?.typeName,
                 `is`(USER_TYPE_NAME)
             )
             assertThat(
-                shortcut.entities["u1"]?.pojo?.typeName?.toJavaPoet(),
+                shortcut.entities["u1"]?.pojo?.typeName,
                 `is`(USER_TYPE_NAME)
             )
             assertThat(
@@ -193,17 +190,15 @@
                 assertThat(shortcut.parameters.size, `is`(1))
                 val param = shortcut.parameters.first()
                 assertThat(
-                    param.type.typeName,
+                    param.type.asTypeName(),
                     `is`(
-                        ParameterizedTypeName.get(
-                            ClassName.get("java.util", "List"), USER_TYPE_NAME
-                        ) as TypeName
+                        CommonTypeNames.MUTABLE_LIST.parametrizedBy(USER_TYPE_NAME)
                     )
                 )
-                assertThat(param.pojoType?.typeName, `is`(USER_TYPE_NAME))
+                assertThat(param.pojoType?.asTypeName(), `is`(USER_TYPE_NAME))
                 assertThat(shortcut.entities.size, `is`(1))
                 assertThat(
-                    shortcut.entities["users"]?.pojo?.typeName?.toJavaPoet(),
+                    shortcut.entities["users"]?.pojo?.typeName,
                     `is`(USER_TYPE_NAME)
                 )
             }
@@ -222,14 +217,14 @@
             assertThat(shortcut.parameters.size, `is`(1))
             val param = shortcut.parameters.first()
             assertThat(
-                param.type.typeName,
+                param.type.asTypeName(),
                 `is`(
-                    ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName
+                    XTypeName.getArrayName(COMMON.USER_TYPE_NAME)
                 )
             )
             assertThat(shortcut.entities.size, `is`(1))
             assertThat(
-                shortcut.entities["users"]?.pojo?.typeName?.toJavaPoet(),
+                shortcut.entities["users"]?.pojo?.typeName,
                 `is`(USER_TYPE_NAME)
             )
         }
@@ -247,17 +242,14 @@
             assertThat(shortcut.parameters.size, `is`(1))
             val param = shortcut.parameters.first()
             assertThat(
-                param.type.typeName,
+                param.type.asTypeName(),
                 `is`(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.util", "Set"),
-                        COMMON.USER_TYPE_NAME
-                    ) as TypeName
+                    CommonTypeNames.MUTABLE_SET.parametrizedBy(COMMON.USER_TYPE_NAME)
                 )
             )
             assertThat(shortcut.entities.size, `is`(1))
             assertThat(
-                shortcut.entities["users"]?.pojo?.typeName?.toJavaPoet(),
+                shortcut.entities["users"]?.pojo?.typeName,
                 `is`(USER_TYPE_NAME)
             )
         }
@@ -275,17 +267,14 @@
             assertThat(shortcut.parameters.size, `is`(1))
             val param = shortcut.parameters.first()
             assertThat(
-                param.type.typeName,
+                param.type.asTypeName(),
                 `is`(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.lang", "Iterable"),
-                        COMMON.USER_TYPE_NAME
-                    ) as TypeName
+                    Iterable::class.asMutableClassName().parametrizedBy(COMMON.USER_TYPE_NAME)
                 )
             )
             assertThat(shortcut.entities.size, `is`(1))
             assertThat(
-                shortcut.entities["users"]?.pojo?.typeName?.toJavaPoet(),
+                shortcut.entities["users"]?.pojo?.typeName,
                 `is`(USER_TYPE_NAME)
             )
         }
@@ -304,17 +293,16 @@
             assertThat(shortcut.parameters.size, `is`(1))
             val param = shortcut.parameters.first()
             assertThat(
-                param.type.typeName,
+                param.type.asTypeName(),
                 `is`(
-                    ParameterizedTypeName.get(
-                        ClassName.get("foo.bar", "MyClass.MyList"),
-                        STRING.toJavaPoet(), COMMON.USER_TYPE_NAME
-                    ) as TypeName
+                    XClassName.get("foo.bar", "MyClass.MyList").parametrizedBy(
+                        CommonTypeNames.STRING, COMMON.USER_TYPE_NAME
+                    )
                 )
             )
             assertThat(shortcut.entities.size, `is`(1))
             assertThat(
-                shortcut.entities["users"]?.pojo?.typeName?.toJavaPoet(),
+                shortcut.entities["users"]?.pojo?.typeName,
                 `is`(USER_TYPE_NAME)
             )
         }
@@ -342,17 +330,17 @@
             ) { shortcut, _ ->
                 assertThat(shortcut.parameters.size, `is`(2))
                 assertThat(
-                    shortcut.parameters[0].type.typeName.toString(),
+                    shortcut.parameters[0].type.asTypeName().toString(CodeLanguage.JAVA),
                     `is`("foo.bar.User")
                 )
                 assertThat(
-                    shortcut.parameters[1].type.typeName.toString(),
+                    shortcut.parameters[1].type.asTypeName().toString(CodeLanguage.JAVA),
                     `is`("foo.bar.Book")
                 )
                 assertThat(shortcut.parameters.map { it.name }, `is`(listOf("u1", "b1")))
                 assertThat(shortcut.entities.size, `is`(2))
                 assertThat(
-                    shortcut.entities["u1"]?.pojo?.typeName?.toJavaPoet(),
+                    shortcut.entities["u1"]?.pojo?.typeName,
                     `is`(USER_TYPE_NAME)
                 )
                 assertThat(
@@ -453,7 +441,7 @@
             assertThat(param.pojoType?.asTypeName(), `is`(USERNAME_TYPE_NAME))
             assertThat(shortcut.entities.size, `is`(1))
             assertThat(shortcut.entities["username"]?.isPartialEntity, `is`(true))
-            assertThat(shortcut.entities["username"]?.entityTypeName?.toJavaPoet(),
+            assertThat(shortcut.entities["username"]?.entityTypeName,
                 `is`(USER_TYPE_NAME))
             assertThat(
                 shortcut.entities["username"]?.pojo?.typeName,
@@ -620,9 +608,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    ProcessorErrors.shortcutMethodArgumentMustBeAClass(
-                        TypeName.LONG
-                    )
+                    ProcessorErrors.shortcutMethodArgumentMustBeAClass("long")
                 )
             }
         }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/EntityNameMatchingVariationsTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/EntityNameMatchingVariationsTest.kt
index 1c3792d..093dcc2 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/EntityNameMatchingVariationsTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/EntityNameMatchingVariationsTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.processor
 
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.parser.SQLTypeAffinity
 import androidx.room.vo.CallType
 import androidx.room.vo.Field
@@ -60,7 +61,8 @@
                 public void $setterName(int id) { this.$fieldName = id; }
             """
         ) { entity, invocation ->
-            assertThat(entity.type.typeName.toString()).isEqualTo("foo.bar.MyEntity")
+            assertThat(entity.type.asTypeName().toString(CodeLanguage.JAVA))
+                .isEqualTo("foo.bar.MyEntity")
             assertThat(entity.fields.size).isEqualTo(1)
             val field = entity.fields.first()
             val intType = invocation.processingEnv.requireType(TypeName.INT)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
index a4a393c..733cacd 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/FieldProcessorTest.kt
@@ -17,6 +17,8 @@
 package androidx.room.processor
 
 import androidx.room.Entity
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XFieldElement
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
@@ -27,19 +29,15 @@
 import androidx.room.solver.types.ColumnTypeAdapter
 import androidx.room.testing.context
 import androidx.room.vo.Field
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.TypeName
-import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.CoreMatchers.nullValue
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.Mockito.mock
-import java.util.Locale
 
-@Suppress("HasPlatformType")
 @RunWith(JUnit4::class)
 class FieldProcessorTest {
     companion object {
@@ -53,60 +51,77 @@
                 """
         const val ENTITY_SUFFIX = "}"
         val ALL_PRIMITIVES = arrayListOf(
-            TypeName.INT,
-            TypeName.BYTE,
-            TypeName.SHORT,
-            TypeName.LONG,
-            TypeName.CHAR,
-            TypeName.FLOAT,
-            TypeName.DOUBLE
+            XTypeName.PRIMITIVE_INT,
+            XTypeName.PRIMITIVE_BYTE,
+            XTypeName.PRIMITIVE_SHORT,
+            XTypeName.PRIMITIVE_LONG,
+            XTypeName.PRIMITIVE_CHAR,
+            XTypeName.PRIMITIVE_FLOAT,
+            XTypeName.PRIMITIVE_DOUBLE
+        )
+        val ALL_BOXED_PRIMITIVES = arrayListOf(
+            XTypeName.BOXED_INT,
+            XTypeName.BOXED_BYTE,
+            XTypeName.BOXED_SHORT,
+            XTypeName.BOXED_LONG,
+            XTypeName.BOXED_CHAR,
+            XTypeName.BOXED_FLOAT,
+            XTypeName.BOXED_DOUBLE
         )
         val ARRAY_CONVERTER = Source.java(
             "foo.bar.MyConverter",
             """
-            package foo.bar;
-            import androidx.room.*;
-            public class MyConverter {
-            ${
-            ALL_PRIMITIVES.joinToString("\n") {
-                val arrayDef = "$it[]"
-                "@TypeConverter public static String" +
-                    " arrayIntoString($arrayDef input) { return null;}" +
-                    "@TypeConverter public static $arrayDef" +
-                    " stringIntoArray$it(String input) { return null;}"
+            |package foo.bar;
+            |import androidx.room.*;
+            |public class MyConverter {
+            |${
+                ALL_PRIMITIVES.joinToString("|\n") {
+                    val arrayName = it.toString(CodeLanguage.JAVA)
+                    val arrayDef = "$arrayName[]"
+                    """
+                    |  @TypeConverter
+                    |  public static String arrayIntoString($arrayDef input) { return null; }
+                    |  @TypeConverter
+                    |  public static $arrayDef stringIntoArray$arrayName(String input) { return null; }
+                    """
+                }
             }
+            |${
+                ALL_BOXED_PRIMITIVES.joinToString("|\n") {
+                    val arrayName = it.simpleNames.single()
+                    val arrayDef = "$arrayName[]"
+                    """
+                    |  @TypeConverter
+                    |  public static String arrayIntoString($arrayDef input) { return null; }
+                    |  @TypeConverter
+                    |  public static $arrayDef stringIntoArray$arrayName(String input) { return null; }
+                    """
+                }
             }
-            ${
-            ALL_PRIMITIVES.joinToString("\n") {
-                val arrayDef = "${it.box()}[]"
-                "@TypeConverter public static String" +
-                    " arrayIntoString($arrayDef input) { return null;}" +
-                    "@TypeConverter public static $arrayDef" +
-                    " stringIntoArray${it}Boxed(String input) { return null;}"
-            }
-            }
-            }
-            """
+            |}
+            """.trimMargin()
         )
 
-        private fun TypeName.affinity(): SQLTypeAffinity {
+        private fun XTypeName.affinity(): SQLTypeAffinity {
             return when (this) {
-                TypeName.FLOAT, TypeName.DOUBLE -> SQLTypeAffinity.REAL
+                XTypeName.PRIMITIVE_FLOAT, XTypeName.BOXED_FLOAT,
+                XTypeName.PRIMITIVE_DOUBLE, XTypeName.BOXED_DOUBLE -> SQLTypeAffinity.REAL
                 else -> SQLTypeAffinity.INTEGER
             }
         }
 
-        private fun TypeName.box(invocation: XTestInvocation) =
+        private fun XTypeName.box(invocation: XTestInvocation) =
             typeMirror(invocation).boxed()
 
-        private fun TypeName.typeMirror(invocation: XTestInvocation) =
+        private fun XTypeName.typeMirror(invocation: XTestInvocation) =
             invocation.processingEnv.requireType(this)
     }
 
     @Test
     fun primitives() {
         ALL_PRIMITIVES.forEach { primitive ->
-            singleEntity("$primitive x;") { field, invocation ->
+            val fieldName = primitive.toString(CodeLanguage.JAVA)
+            singleEntity("$fieldName x;") { field, invocation ->
                 assertThat(
                     field,
                     `is`(
@@ -124,16 +139,17 @@
 
     @Test
     fun boxed() {
-        ALL_PRIMITIVES.forEach { primitive ->
-            singleEntity("@Nullable ${primitive.box()} y;") { field, invocation ->
+        ALL_BOXED_PRIMITIVES.forEach { boxedPrimitive ->
+            val fieldName = boxedPrimitive.simpleNames.single()
+            singleEntity("@Nullable $fieldName y;") { field, invocation ->
                 assertThat(
                     field,
                     `is`(
                         Field(
                             name = "y",
-                            type = primitive.box(invocation).makeNullable(),
+                            type = boxedPrimitive.typeMirror(invocation).makeNullable(),
                             element = field.element,
-                            affinity = primitive.affinity()
+                            affinity = boxedPrimitive.affinity()
                         )
                     )
                 )
@@ -155,7 +171,7 @@
                 `is`(
                     Field(
                         name = "x",
-                        type = TypeName.INT.typeMirror(invocation),
+                        type = XTypeName.PRIMITIVE_INT.typeMirror(invocation),
                         element = field.element,
                         columnName = "foo",
                         affinity = SQLTypeAffinity.INTEGER
@@ -178,7 +194,7 @@
                 `is`(
                     Field(
                         name = "x",
-                        type = TypeName.INT.typeMirror(invocation),
+                        type = XTypeName.PRIMITIVE_INT.typeMirror(invocation),
                         element = field.element,
                         columnName = "foo",
                         affinity = SQLTypeAffinity.INTEGER,
@@ -216,7 +232,7 @@
                 `is`(
                     Field(
                         name = "arr",
-                        type = invocation.processingEnv.getArrayType(TypeName.BYTE)
+                        type = invocation.processingEnv.getArrayType(XTypeName.PRIMITIVE_BYTE)
                             .makeNonNullable(),
                         element = field.element,
                         affinity = SQLTypeAffinity.TEXT
@@ -235,7 +251,7 @@
         ALL_PRIMITIVES.forEach { primitive ->
             singleEntity(
                 "@TypeConverters(foo.bar.MyConverter.class) @NonNull " +
-                    "${primitive.toString().lowercase(Locale.US)}[] arr;"
+                    "${primitive.toString(CodeLanguage.JAVA)}[] arr;"
             ) { field, invocation ->
                 assertThat(
                     field,
@@ -245,7 +261,7 @@
                             type = invocation.processingEnv.getArrayType(primitive)
                                 .makeNonNullable(),
                             element = field.element,
-                            affinity = if (primitive == TypeName.BYTE) {
+                            affinity = if (primitive == XTypeName.PRIMITIVE_BYTE) {
                                 SQLTypeAffinity.BLOB
                             } else {
                                 SQLTypeAffinity.TEXT
@@ -259,16 +275,14 @@
 
     @Test
     fun boxedArray() {
-        ALL_PRIMITIVES.forEach { primitive ->
+        ALL_BOXED_PRIMITIVES.forEach { boxedPrimitive ->
             singleEntity(
                 "@TypeConverters(foo.bar.MyConverter.class) " +
-                    "${primitive.box()}[] arr;"
+                    "${boxedPrimitive.toString(CodeLanguage.JAVA)}[] arr;"
             ) { field, invocation ->
                 val expected = Field(
                     name = "arr",
-                    type = invocation.processingEnv.getArrayType(
-                        primitive.box()
-                    ),
+                    type = invocation.processingEnv.getArrayType(boxedPrimitive),
                     element = field.element,
                     affinity = SQLTypeAffinity.TEXT,
                     nonNull = false // no annotation
@@ -287,8 +301,8 @@
                     )
                 )
                 assertThat(
-                    field.type.typeName,
-                    `is`(ArrayTypeName.of(primitive.box()))
+                    field.type.asTypeName(),
+                    `is`(XTypeName.getArrayName(boxedPrimitive))
                 )
             }
         }
@@ -312,7 +326,7 @@
                 `is`(
                     Field(
                         name = "item",
-                        type = TypeName.INT.box(invocation).makeNonNullable(),
+                        type = XTypeName.PRIMITIVE_INT.box(invocation).makeNonNullable(),
                         element = field.element,
                         affinity = SQLTypeAffinity.INTEGER
                     )
@@ -350,14 +364,14 @@
             val fieldElement = mock(XFieldElement::class.java)
             assertThat(
                 Field(
-                    fieldElement, "x", TypeName.INT.typeMirror(it),
+                    fieldElement, "x", XTypeName.PRIMITIVE_INT.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("x"))
             )
             assertThat(
                 Field(
-                    fieldElement, "x", TypeName.BOOLEAN.typeMirror(it),
+                    fieldElement, "x", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("x"))
@@ -365,7 +379,7 @@
             assertThat(
                 Field(
                     fieldElement, "xAll",
-                    TypeName.BOOLEAN.typeMirror(it), SQLTypeAffinity.INTEGER
+                    XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it), SQLTypeAffinity.INTEGER
                 )
                     .nameWithVariations,
                 `is`(arrayListOf("xAll"))
@@ -379,28 +393,28 @@
         runProcessorTest {
             assertThat(
                 Field(
-                    elm, "isX", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "isX", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("isX", "x"))
             )
             assertThat(
                 Field(
-                    elm, "isX", TypeName.INT.typeMirror(it),
+                    elm, "isX", XTypeName.PRIMITIVE_INT.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("isX"))
             )
             assertThat(
                 Field(
-                    elm, "is", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "is", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("is"))
             )
             assertThat(
                 Field(
-                    elm, "isAllItems", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "isAllItems", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("isAllItems", "allItems"))
@@ -414,28 +428,28 @@
         runProcessorTest {
             assertThat(
                 Field(
-                    elm, "hasX", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "hasX", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("hasX", "x"))
             )
             assertThat(
                 Field(
-                    elm, "hasX", TypeName.INT.typeMirror(it),
+                    elm, "hasX", XTypeName.PRIMITIVE_INT.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("hasX"))
             )
             assertThat(
                 Field(
-                    elm, "has", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "has", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("has"))
             )
             assertThat(
                 Field(
-                    elm, "hasAllItems", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "hasAllItems", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("hasAllItems", "allItems"))
@@ -449,42 +463,42 @@
         runProcessorTest {
             assertThat(
                 Field(
-                    elm, "mall", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "mall", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("mall"))
             )
             assertThat(
                 Field(
-                    elm, "mallVars", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "mallVars", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("mallVars"))
             )
             assertThat(
                 Field(
-                    elm, "mAll", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "mAll", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("mAll", "all"))
             )
             assertThat(
                 Field(
-                    elm, "m", TypeName.INT.typeMirror(it),
+                    elm, "m", XTypeName.PRIMITIVE_INT.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("m"))
             )
             assertThat(
                 Field(
-                    elm, "mallItems", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "mallItems", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("mallItems"))
             )
             assertThat(
                 Field(
-                    elm, "mAllItems", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "mAllItems", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("mAllItems", "allItems"))
@@ -498,21 +512,21 @@
         runProcessorTest {
             assertThat(
                 Field(
-                    elm, "_all", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "_all", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("_all", "all"))
             )
             assertThat(
                 Field(
-                    elm, "_", TypeName.INT.typeMirror(it),
+                    elm, "_", XTypeName.PRIMITIVE_INT.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("_"))
             )
             assertThat(
                 Field(
-                    elm, "_allItems", TypeName.BOOLEAN.typeMirror(it),
+                    elm, "_allItems", XTypeName.PRIMITIVE_BOOLEAN.typeMirror(it),
                     SQLTypeAffinity.INTEGER
                 ).nameWithVariations,
                 `is`(arrayListOf("_allItems", "allItems"))
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts3TableEntityProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts3TableEntityProcessorTest.kt
index bf4a2d5..5fe3711 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts3TableEntityProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts3TableEntityProcessorTest.kt
@@ -33,6 +33,7 @@
 package androidx.room.processor
 
 import androidx.room.FtsOptions
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.parser.FtsVersion
 import androidx.room.parser.SQLTypeAffinity
 import androidx.room.vo.CallType
@@ -63,7 +64,8 @@
                 public void setRowId(int id) { this.rowId = rowId; }
             """
         ) { entity, invocation ->
-            assertThat(entity.type.typeName.toString(), `is`("foo.bar.MyEntity"))
+            assertThat(entity.type.asTypeName().toString(CodeLanguage.JAVA),
+                `is`("foo.bar.MyEntity"))
             assertThat(entity.fields.size, `is`(1))
             val field = entity.fields.first()
             val intType = invocation.processingEnv.requireType(TypeName.INT)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
index 3434fdf..9b1f407 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/Fts4TableEntityProcessorTest.kt
@@ -17,6 +17,7 @@
 package androidx.room.processor
 
 import androidx.room.FtsOptions
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.parser.FtsVersion
@@ -50,7 +51,8 @@
                 public void setRowId(int id) { this.rowId = rowId; }
             """
         ) { entity, invocation ->
-            assertThat(entity.type.typeName.toString(), `is`("foo.bar.MyEntity"))
+            assertThat(entity.type.asTypeName().toString(CodeLanguage.JAVA),
+                `is`("foo.bar.MyEntity"))
             assertThat(entity.fields.size, `is`(1))
             val field = entity.fields.first()
             val intType = invocation.processingEnv.requireType(TypeName.INT)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
index 4458240..a238f1f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
@@ -18,8 +18,11 @@
 
 import COMMON
 import androidx.room.Dao
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.codegen.asClassName
+import androidx.room.compiler.codegen.asMutableClassName
 import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XType
@@ -38,10 +41,6 @@
 import androidx.room.testing.context
 import androidx.room.vo.InsertOrUpsertShortcutMethod
 import com.google.common.truth.Truth.assertThat
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
 import kotlin.reflect.KClass
 import org.junit.Test
 
@@ -74,7 +73,7 @@
                 abstract class MyClass {
                 """
         const val DAO_SUFFIX = "}"
-        val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
+        val USER_TYPE_NAME: XTypeName = COMMON.USER_TYPE_NAME
         val USERNAME_TYPE_NAME: XTypeName = XClassName.get("foo.bar", "Username")
         val BOOK_TYPE_NAME: XTypeName = XClassName.get("foo.bar", "Book")
     }
@@ -89,7 +88,7 @@
         ) { insertionUpsertion, invocation ->
             assertThat(insertionUpsertion.element.jvmName).isEqualTo("foo")
             assertThat(insertionUpsertion.parameters.size).isEqualTo(0)
-            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+            assertThat(insertionUpsertion.returnType.asTypeName()).isEqualTo(XTypeName.UNIT_VOID)
             assertThat(insertionUpsertion.entities.size).isEqualTo(0)
             invocation.assertCompilationResult {
                 hasErrorContaining(noParamsError())
@@ -131,20 +130,20 @@
             assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
 
             val param = insertionUpsertion.parameters.first()
-            assertThat(param.type.typeName)
+            assertThat(param.type.asTypeName())
                 .isEqualTo(USER_TYPE_NAME)
 
-            assertThat(param.pojoType?.typeName)
+            assertThat(param.pojoType?.asTypeName())
                 .isEqualTo(USER_TYPE_NAME)
 
             assertThat(insertionUpsertion.entities["user"]?.isPartialEntity)
                 .isEqualTo(false)
 
-            assertThat(insertionUpsertion.entities["user"]?.pojo?.typeName?.toJavaPoet())
-                .isEqualTo(ClassName.get("foo.bar", "User") as TypeName)
+            assertThat(insertionUpsertion.entities["user"]?.pojo?.typeName)
+                .isEqualTo(XClassName.get("foo.bar", "User"))
 
-            assertThat(insertionUpsertion.returnType.typeName)
-                .isEqualTo(TypeName.LONG)
+            assertThat(insertionUpsertion.returnType.asTypeName())
+                .isEqualTo(XTypeName.PRIMITIVE_LONG)
         }
     }
     @Test
@@ -159,23 +158,23 @@
 
             assertThat(insertionUpsertion.parameters.size).isEqualTo(2)
             insertionUpsertion.parameters.forEach {
-                assertThat(it.type.typeName).isEqualTo(USER_TYPE_NAME)
-                assertThat(it.pojoType?.typeName).isEqualTo(USER_TYPE_NAME)
+                assertThat(it.type.asTypeName()).isEqualTo(USER_TYPE_NAME)
+                assertThat(it.pojoType?.asTypeName()).isEqualTo(USER_TYPE_NAME)
             }
             assertThat(insertionUpsertion.entities.size)
                 .isEqualTo(2)
 
-            assertThat(insertionUpsertion.entities["u1"]?.pojo?.typeName?.toJavaPoet())
+            assertThat(insertionUpsertion.entities["u1"]?.pojo?.typeName)
                 .isEqualTo(USER_TYPE_NAME)
 
-            assertThat(insertionUpsertion.entities["u2"]?.pojo?.typeName?.toJavaPoet())
+            assertThat(insertionUpsertion.entities["u2"]?.pojo?.typeName)
                 .isEqualTo(USER_TYPE_NAME)
 
             assertThat(insertionUpsertion.parameters.map { it.name })
                 .isEqualTo(listOf("u1", "u2"))
 
-            assertThat(insertionUpsertion.returnType.typeName)
-                .isEqualTo(TypeName.VOID)
+            assertThat(insertionUpsertion.returnType.asTypeName())
+                .isEqualTo(XTypeName.UNIT_VOID)
         }
     }
 
@@ -190,28 +189,22 @@
             assertThat(insertionUpsertion.element.jvmName).isEqualTo("insertUsers")
             assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
             val param = insertionUpsertion.parameters.first()
-            assertThat(param.type.typeName)
+            assertThat(param.type.asTypeName())
                 .isEqualTo(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.util", "List"),
-                        USER_TYPE_NAME
-                    ) as TypeName
+                    CommonTypeNames.MUTABLE_LIST.parametrizedBy(USER_TYPE_NAME)
                 )
 
-            assertThat(param.pojoType?.typeName)
+            assertThat(param.pojoType?.asTypeName())
                 .isEqualTo(USER_TYPE_NAME)
 
             assertThat(insertionUpsertion.entities.size).isEqualTo(1)
 
-            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName?.toJavaPoet())
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
                 .isEqualTo(USER_TYPE_NAME)
 
-            assertThat(insertionUpsertion.returnType.typeName)
+            assertThat(insertionUpsertion.returnType.asTypeName())
                 .isEqualTo(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.util", "List"),
-                        ClassName.get("java.lang", "Long")
-                    ) as TypeName
+                    CommonTypeNames.MUTABLE_LIST.parametrizedBy(XTypeName.BOXED_LONG)
                 )
         }
     }
@@ -227,16 +220,16 @@
             assertThat(insertionUpsertion.element.jvmName).isEqualTo("insertUsers")
             assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
             val param = insertionUpsertion.parameters.first()
-            assertThat(param.type.typeName)
-                .isEqualTo(ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName)
+            assertThat(param.type.asTypeName())
+                .isEqualTo(XTypeName.getArrayName(COMMON.USER_TYPE_NAME))
 
             assertThat(insertionUpsertion.entities.size).isEqualTo(1)
 
-            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName?.toJavaPoet())
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
                 .isEqualTo(USER_TYPE_NAME)
 
-            assertThat(insertionUpsertion.returnType.typeName)
-                .isEqualTo(TypeName.VOID)
+            assertThat(insertionUpsertion.returnType.asTypeName())
+                .isEqualTo(XTypeName.UNIT_VOID)
         }
     }
 
@@ -251,20 +244,17 @@
             assertThat(insertionUpsertion.element.jvmName).isEqualTo("insertUsers")
             assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
             val param = insertionUpsertion.parameters.first()
-            assertThat(param.type.typeName)
+            assertThat(param.type.asTypeName())
                 .isEqualTo(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.util", "Set"),
-                        COMMON.USER_TYPE_NAME
-                    ) as TypeName
+                    CommonTypeNames.MUTABLE_SET.parametrizedBy(COMMON.USER_TYPE_NAME)
                 )
 
             assertThat(insertionUpsertion.entities.size).isEqualTo(1)
 
-            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName?.toJavaPoet())
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
                 .isEqualTo(USER_TYPE_NAME)
 
-            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+            assertThat(insertionUpsertion.returnType.asTypeName()).isEqualTo(XTypeName.UNIT_VOID)
         }
     }
 
@@ -279,20 +269,17 @@
             assertThat(insertionUpsertion.element.jvmName).isEqualTo("insertUsers")
             assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
             val param = insertionUpsertion.parameters.first()
-            assertThat(param.type.typeName)
+            assertThat(param.type.asTypeName())
                 .isEqualTo(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.util", "Queue"),
-                        USER_TYPE_NAME
-                    ) as TypeName
+                    java.util.Queue::class.asClassName().parametrizedBy(USER_TYPE_NAME)
                 )
 
             assertThat(insertionUpsertion.entities.size).isEqualTo(1)
 
-            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName?.toJavaPoet())
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
                 .isEqualTo(USER_TYPE_NAME)
 
-            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+            assertThat(insertionUpsertion.returnType.asTypeName()).isEqualTo(XTypeName.UNIT_VOID)
         }
     }
 
@@ -307,20 +294,17 @@
             assertThat(insertionUpsertion.element.jvmName).isEqualTo("insert")
             assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
             val param = insertionUpsertion.parameters.first()
-            assertThat(param.type.typeName)
+            assertThat(param.type.asTypeName())
                 .isEqualTo(
-                    ParameterizedTypeName.get(
-                        ClassName.get("java.lang", "Iterable"),
-                        USER_TYPE_NAME
-                    ) as TypeName
+                    Iterable::class.asMutableClassName().parametrizedBy(USER_TYPE_NAME)
                 )
 
             assertThat(insertionUpsertion.entities.size).isEqualTo(1)
 
-            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName?.toJavaPoet())
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
                 .isEqualTo(USER_TYPE_NAME)
 
-            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+            assertThat(insertionUpsertion.returnType.asTypeName()).isEqualTo(XTypeName.UNIT_VOID)
         }
     }
 
@@ -336,20 +320,19 @@
             assertThat(insertionUpsertion.element.jvmName).isEqualTo("insert")
             assertThat(insertionUpsertion.parameters.size).isEqualTo(1)
             val param = insertionUpsertion.parameters.first()
-            assertThat(param.type.typeName)
+            assertThat(param.type.asTypeName())
                 .isEqualTo(
-                    ParameterizedTypeName.get(
-                        ClassName.get("foo.bar", "MyClass.MyList"),
-                        CommonTypeNames.STRING.toJavaPoet(), USER_TYPE_NAME
-                    ) as TypeName
+                    XClassName.get("foo.bar", "MyClass.MyList").parametrizedBy(
+                        CommonTypeNames.STRING, USER_TYPE_NAME
+                    )
                 )
 
             assertThat(insertionUpsertion.entities.size).isEqualTo(1)
 
-            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName?.toJavaPoet())
+            assertThat(insertionUpsertion.entities["users"]?.pojo?.typeName)
                 .isEqualTo(USER_TYPE_NAME)
 
-            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+            assertThat(insertionUpsertion.returnType.asTypeName()).isEqualTo(XTypeName.UNIT_VOID)
         }
     }
 
@@ -362,19 +345,21 @@
                 """
         ) { insertionUpsertion, _ ->
             assertThat(insertionUpsertion.parameters.size).isEqualTo(2)
-            assertThat(insertionUpsertion.parameters[0].type.typeName.toString())
-                .isEqualTo("foo.bar.User")
+            assertThat(
+                insertionUpsertion.parameters[0].type.asTypeName().toString(CodeLanguage.JAVA)
+            ).isEqualTo("foo.bar.User")
 
-            assertThat(insertionUpsertion.parameters[1].type.typeName.toString())
-                .isEqualTo("foo.bar.Book")
+            assertThat(
+                insertionUpsertion.parameters[1].type.asTypeName().toString(CodeLanguage.JAVA)
+            ).isEqualTo("foo.bar.Book")
 
             assertThat(insertionUpsertion.parameters.map { it.name }).isEqualTo(listOf("u1", "b1"))
 
-            assertThat(insertionUpsertion.returnType.typeName).isEqualTo(TypeName.VOID)
+            assertThat(insertionUpsertion.returnType.asTypeName()).isEqualTo(XTypeName.UNIT_VOID)
 
             assertThat(insertionUpsertion.entities.size).isEqualTo(2)
 
-            assertThat(insertionUpsertion.entities["u1"]?.pojo?.typeName?.toJavaPoet())
+            assertThat(insertionUpsertion.entities["u1"]?.pojo?.typeName)
                 .isEqualTo(USER_TYPE_NAME)
 
             assertThat(insertionUpsertion.entities["b1"]?.pojo?.typeName)
@@ -593,7 +578,7 @@
             assertThat(insertionUpsertion.entities["username"]?.isPartialEntity)
                 .isEqualTo(true)
 
-            assertThat(insertionUpsertion.entities["username"]?.entityTypeName?.toJavaPoet())
+            assertThat(insertionUpsertion.entities["username"]?.entityTypeName)
                 .isEqualTo(USER_TYPE_NAME)
 
             assertThat(insertionUpsertion.entities["username"]?.pojo?.typeName)
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
index 2785605..639ba0d 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
@@ -687,7 +687,7 @@
             assertThat(pojo.relations.size, `is`(1))
             val rel = pojo.relations.first()
             assertThat(rel.projection, `is`(listOf("uid")))
-            assertThat(rel.entity.typeName.toJavaPoet(), `is`(COMMON.USER_TYPE_NAME as TypeName))
+            assertThat(rel.entity.typeName, `is`(COMMON.USER_TYPE_NAME))
         }
     }
 
@@ -705,7 +705,7 @@
             assertThat(pojo.relations.size, `is`(1))
             val rel = pojo.relations.first()
             assertThat(rel.projection, `is`(listOf("name")))
-            assertThat(rel.entity.typeName.toJavaPoet(), `is`(COMMON.USER_TYPE_NAME as TypeName))
+            assertThat(rel.entity.typeName, `is`(COMMON.USER_TYPE_NAME))
         }
     }
 
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index 78038eb..4cd4627 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -19,12 +19,16 @@
 import COMMON
 import androidx.room.Dao
 import androidx.room.Query
+import androidx.room.compiler.codegen.XClassName
+import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.runProcessorTest
+import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.CommonTypeNames.LIST
 import androidx.room.ext.GuavaUtilConcurrentTypeNames
 import androidx.room.ext.KotlinTypeNames
@@ -54,7 +58,6 @@
 import androidx.room.vo.Warning
 import androidx.room.vo.WriteQueryMethod
 import com.google.common.truth.Truth.assertThat
-import com.squareup.javapoet.ArrayTypeName
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeName
@@ -102,7 +105,7 @@
                 abstract class MyClass {
                 """
         const val DAO_SUFFIX = "}"
-        val POJO: ClassName = ClassName.get("foo.bar", "MyClass.Pojo")
+        val POJO = XClassName.get("foo.bar", "MyClass.Pojo")
         @Parameterized.Parameters(name = "enableDbVerification={0}")
         @JvmStatic
         fun getParams() = arrayOf(true, false)
@@ -130,8 +133,8 @@
             assertThat(parsedQuery.element.jvmName, `is`("foo"))
             assertThat(parsedQuery.parameters.size, `is`(0))
             assertThat(
-                parsedQuery.returnType.typeName,
-                `is`(ArrayTypeName.of(TypeName.INT) as TypeName)
+                parsedQuery.returnType.asTypeName(),
+                `is`(XTypeName.getArrayName(XTypeName.PRIMITIVE_INT))
             )
         }
     }
@@ -145,7 +148,7 @@
                 """
         ) { parsedQuery, invocation ->
             assertThat(parsedQuery.element.jvmName, `is`("foo"))
-            assertThat(parsedQuery.returnType.typeName, `is`(TypeName.LONG))
+            assertThat(parsedQuery.returnType.asTypeName(), `is`(XTypeName.PRIMITIVE_LONG))
             assertThat(parsedQuery.parameters.size, `is`(1))
             val param = parsedQuery.parameters.first()
             assertThat(param.name, `is`("x"))
@@ -166,14 +169,14 @@
                 """
         ) { parsedQuery, _ ->
             assertThat(parsedQuery.element.jvmName, `is`("foo"))
-            assertThat(parsedQuery.returnType.typeName, `is`(TypeName.LONG))
+            assertThat(parsedQuery.returnType.asTypeName(), `is`(XTypeName.PRIMITIVE_LONG))
             assertThat(parsedQuery.parameters.size, `is`(1))
             val param = parsedQuery.parameters.first()
             assertThat(param.name, `is`("ids"))
             assertThat(param.sqlName, `is`("ids"))
             assertThat(
-                param.type.typeName,
-                `is`(ArrayTypeName.of(TypeName.INT))
+                param.type.asTypeName(),
+                `is`(XTypeName.getArrayName(XTypeName.PRIMITIVE_INT))
             )
         }
     }
@@ -333,7 +336,7 @@
         singleQueryMethod<ReadQueryMethod>(
             """
                 @Query("select * from User")
-                abstract public <T> ${LIST.toJavaPoet()}<T> foo(int x);
+                abstract public <T> ${LIST.canonicalName}<T> foo(int x);
                 """
         ) { parsedQuery, invocation ->
             val expected: TypeName = ParameterizedTypeName.get(
@@ -431,8 +434,8 @@
                 """
         ) { parsedQuery, _ ->
             assertThat(
-                parsedQuery.returnType.typeName,
-                `is`(ClassName.get(Integer::class.java) as TypeName)
+                parsedQuery.returnType.asTypeName(),
+                `is`(XTypeName.BOXED_INT)
             )
         }
     }
@@ -451,9 +454,9 @@
                 """
         ) { parsedQuery, _ ->
             assertThat(
-                parsedQuery.parameters.first().type.typeName,
+                parsedQuery.parameters.first().type.asTypeName(),
                 `is`(
-                    TypeName.INT.box()
+                    XTypeName.BOXED_INT
                 )
             )
         }
@@ -470,7 +473,7 @@
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     ProcessorErrors
-                        .cannotFindPreparedQueryResultAdapter(TypeName.FLOAT, QueryType.DELETE)
+                        .cannotFindPreparedQueryResultAdapter("float", QueryType.DELETE)
                 )
             }
         }
@@ -486,7 +489,7 @@
         ) { parsedQuery, _ ->
             assertThat(parsedQuery.element.jvmName, `is`("foo"))
             assertThat(parsedQuery.parameters.size, `is`(1))
-            assertThat(parsedQuery.returnType.typeName, `is`(TypeName.INT))
+            assertThat(parsedQuery.returnType.asTypeName(), `is`(XTypeName.PRIMITIVE_INT))
         }
     }
 
@@ -500,7 +503,7 @@
         ) { parsedQuery, _ ->
             assertThat(parsedQuery.element.jvmName, `is`("foo"))
             assertThat(parsedQuery.parameters.size, `is`(1))
-            assertThat(parsedQuery.returnType.typeName, `is`(TypeName.VOID))
+            assertThat(parsedQuery.returnType.asTypeName(), `is`(XTypeName.UNIT_VOID))
         }
     }
 
@@ -511,13 +514,13 @@
                 @Query("update user set name = :name")
                 abstract public void updateAllNames(String name);
                 """
-        ) { parsedQuery, invocation ->
+        ) { parsedQuery, _ ->
             assertThat(parsedQuery.element.jvmName, `is`("updateAllNames"))
             assertThat(parsedQuery.parameters.size, `is`(1))
-            assertThat(parsedQuery.returnType.typeName, `is`(TypeName.VOID))
+            assertThat(parsedQuery.returnType.asTypeName(), `is`(XTypeName.UNIT_VOID))
             assertThat(
-                parsedQuery.parameters.first().type.typeName,
-                `is`(invocation.context.COMMON_TYPES.STRING.typeName)
+                parsedQuery.parameters.first().type.asTypeName(),
+                `is`(CommonTypeNames.STRING)
             )
         }
     }
@@ -529,13 +532,13 @@
                 @Query("insert into user (name) values (:name)")
                 abstract public void insertUsername(String name);
                 """
-        ) { parsedQuery, invocation ->
+        ) { parsedQuery, _ ->
             assertThat(parsedQuery.element.jvmName, `is`("insertUsername"))
             assertThat(parsedQuery.parameters.size, `is`(1))
-            assertThat(parsedQuery.returnType.typeName, `is`(TypeName.VOID))
+            assertThat(parsedQuery.returnType.asTypeName(), `is`(XTypeName.UNIT_VOID))
             assertThat(
-                parsedQuery.parameters.first().type.typeName,
-                `is`(invocation.context.COMMON_TYPES.STRING.typeName)
+                parsedQuery.parameters.first().type.asTypeName(),
+                `is`(CommonTypeNames.STRING)
             )
         }
     }
@@ -547,13 +550,13 @@
                 @Query("insert into user (name) values (:name)")
                 abstract public long insertUsername(String name);
                 """
-        ) { parsedQuery, invocation ->
+        ) { parsedQuery, _ ->
             assertThat(parsedQuery.element.jvmName, `is`("insertUsername"))
             assertThat(parsedQuery.parameters.size, `is`(1))
-            assertThat(parsedQuery.returnType.typeName, `is`(TypeName.LONG))
+            assertThat(parsedQuery.returnType.asTypeName(), `is`(XTypeName.PRIMITIVE_LONG))
             assertThat(
-                parsedQuery.parameters.first().type.typeName,
-                `is`(invocation.context.COMMON_TYPES.STRING.typeName)
+                parsedQuery.parameters.first().type.asTypeName(),
+                `is`(CommonTypeNames.STRING)
             )
         }
     }
@@ -566,11 +569,11 @@
                 abstract public int insert(String name);
                 """
         ) { parsedQuery, invocation ->
-            assertThat(parsedQuery.returnType.typeName, `is`(TypeName.INT))
+            assertThat(parsedQuery.returnType.asTypeName(), `is`(XTypeName.PRIMITIVE_INT))
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     ProcessorErrors
-                        .cannotFindPreparedQueryResultAdapter(TypeName.INT, QueryType.INSERT)
+                        .cannotFindPreparedQueryResultAdapter("int", QueryType.INSERT)
                 )
             }
         }
@@ -611,10 +614,7 @@
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     ProcessorErrors.cannotFindPreparedQueryResultAdapter(
-                        ParameterizedTypeName.get(
-                            LifecyclesTypeNames.LIVE_DATA,
-                            TypeName.INT.box()
-                        ),
+                        "androidx.lifecycle.LiveData<java.lang.Integer>",
                         QueryType.DELETE
                     )
                 )
@@ -633,10 +633,7 @@
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     ProcessorErrors.cannotFindPreparedQueryResultAdapter(
-                        ParameterizedTypeName.get(
-                            LifecyclesTypeNames.LIVE_DATA,
-                            TypeName.INT.box()
-                        ),
+                        "androidx.lifecycle.LiveData<java.lang.Integer>",
                         QueryType.UPDATE
                     )
                 )
@@ -796,8 +793,8 @@
             assertThat(parsedQuery.element.jvmName, `is`("foo"))
             assertThat(parsedQuery.parameters.size, `is`(0))
             assertThat(
-                parsedQuery.returnType.typeName,
-                `is`(ArrayTypeName.of(TypeName.INT) as TypeName)
+                parsedQuery.returnType.asTypeName(),
+                `is`(XTypeName.getArrayName(XTypeName.PRIMITIVE_INT))
             )
         }
     }
@@ -814,8 +811,8 @@
             assertThat(parsedQuery.element.jvmName, `is`("getPojo"))
             assertThat(parsedQuery.parameters.size, `is`(0))
             assertThat(
-                parsedQuery.returnType.typeName,
-                `is`(COMMON.NOT_AN_ENTITY_TYPE_NAME as TypeName)
+                parsedQuery.returnType.asTypeName(),
+                `is`(COMMON.NOT_AN_ENTITY_TYPE_NAME)
             )
             val adapter = parsedQuery.queryResultBinder.adapter
             assertThat(checkNotNull(adapter))
@@ -873,12 +870,9 @@
             val pojoRowAdapter = listAdapter.rowAdapters.single() as PojoRowAdapter
             assertThat(pojoRowAdapter.relationCollectors.size, `is`(1))
             assertThat(
-                pojoRowAdapter.relationCollectors[0].relationTypeName.toJavaPoet(),
+                pojoRowAdapter.relationCollectors[0].relationTypeName,
                 `is`(
-                    ParameterizedTypeName.get(
-                        ClassName.get(ArrayList::class.java),
-                        COMMON.USER_TYPE_NAME
-                    ) as TypeName
+                    CommonTypeNames.ARRAY_LIST.parametrizedBy(COMMON.USER_TYPE_NAME)
                 )
             )
             invocation.assertCompilationResult {
@@ -1098,15 +1092,15 @@
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     cannotFindQueryResultAdapter(
-                        ClassName.get("foo.bar", "MyClass", "Pojo")
+                        XClassName.get("foo.bar", "MyClass", "Pojo").canonicalName
                     )
                 )
                 hasWarningContaining(
                     ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeNames = listOf(POJO),
+                        pojoTypeNames = listOf(POJO.canonicalName),
                         unusedColumns = listOf("name", "lastName"),
                         pojoUnusedFields = mapOf(
-                            POJO to listOf(
+                            POJO.canonicalName to listOf(
                                 createField("nameX"),
                                 createField("lastNameX")
                             )
@@ -1133,7 +1127,7 @@
                 hasErrorContaining("no such column: age")
                 hasErrorContaining(
                     cannotFindQueryResultAdapter(
-                        ClassName.get("foo.bar", "MyClass", "Pojo")
+                        XClassName.get("foo.bar", "MyClass", "Pojo").canonicalName
                     )
                 )
                 hasErrorCount(2)
@@ -1156,7 +1150,7 @@
             invocation.assertCompilationResult {
                 hasWarningContaining(
                     ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeNames = listOf(POJO),
+                        pojoTypeNames = listOf(POJO.canonicalName),
                         unusedColumns = listOf("uid"),
                         pojoUnusedFields = emptyMap(),
                         allColumns = listOf("uid", "name", "lastName"),
@@ -1185,10 +1179,10 @@
             invocation.assertCompilationResult {
                 hasWarningContaining(
                     ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeNames = listOf(POJO),
+                        pojoTypeNames = listOf(POJO.canonicalName),
                         unusedColumns = emptyList(),
                         allColumns = listOf("lastName"),
-                        pojoUnusedFields = mapOf(POJO to listOf(createField("name"))),
+                        pojoUnusedFields = mapOf(POJO.canonicalName to listOf(createField("name"))),
                     )
                 )
             }
@@ -1215,15 +1209,15 @@
             invocation.assertCompilationResult {
                 hasWarningContaining(
                     ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeNames = listOf(POJO),
+                        pojoTypeNames = listOf(POJO.canonicalName),
                         unusedColumns = emptyList(),
-                        pojoUnusedFields = mapOf(POJO to listOf(createField("name"))),
+                        pojoUnusedFields = mapOf(POJO.canonicalName to listOf(createField("name"))),
                         allColumns = listOf("lastName"),
                     )
                 )
                 hasErrorContaining(
                     ProcessorErrors.pojoMissingNonNull(
-                        pojoTypeName = POJO,
+                        pojoTypeName = POJO.canonicalName,
                         missingPojoFields = listOf("name"),
                         allQueryColumns = listOf("lastName")
                     )
@@ -1251,10 +1245,12 @@
             invocation.assertCompilationResult {
                 hasWarningContaining(
                     ProcessorErrors.cursorPojoMismatch(
-                        pojoTypeNames = listOf(POJO),
+                        pojoTypeNames = listOf(POJO.canonicalName),
                         unusedColumns = listOf("uid"),
                         allColumns = listOf("uid", "name"),
-                        pojoUnusedFields = mapOf(POJO to listOf(createField("lastName")))
+                        pojoUnusedFields = mapOf(
+                            POJO.canonicalName to listOf(createField("lastName"))
+                        )
                     )
                 )
             }
@@ -1558,9 +1554,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "String")
-                    )
+                    valueMayNeedMapInfo(String::class.asClassName().canonicalName)
                 )
             }
         }
@@ -1577,9 +1571,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "String")
-                    )
+                    valueMayNeedMapInfo(String::class.asClassName().canonicalName)
                 )
             }
         }
@@ -1595,9 +1587,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "String")
-                    )
+                    valueMayNeedMapInfo(String::class.asClassName().canonicalName)
                 )
             }
         }
@@ -1613,9 +1603,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "String")
-                    )
+                    valueMayNeedMapInfo(String::class.asClassName().canonicalName)
                 )
             }
         }
@@ -1631,9 +1619,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "Long")
-                    )
+                    valueMayNeedMapInfo(XTypeName.BOXED_LONG.canonicalName)
                 )
             }
         }
@@ -1649,9 +1635,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "Long")
-                    )
+                    valueMayNeedMapInfo(XTypeName.BOXED_LONG.canonicalName)
                 )
             }
         }
@@ -1667,9 +1651,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "Long")
-                    )
+                    valueMayNeedMapInfo(XTypeName.BOXED_LONG.canonicalName)
                 )
             }
         }
@@ -1686,9 +1668,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    keyMayNeedMapInfo(
-                        ClassName.get("java.util", "Date")
-                    )
+                    keyMayNeedMapInfo("java.util.Date")
                 )
             }
         }
@@ -1705,9 +1685,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    valueMayNeedMapInfo(
-                        ClassName.get("java.util", "Date")
-                    )
+                    valueMayNeedMapInfo("java.util.Date")
                 )
             }
         }
@@ -1809,7 +1787,7 @@
                     ProcessorErrors.ambiguousColumn(
                         "uid",
                         ProcessorErrors.AmbiguousColumnLocation.POJO,
-                        ClassName.get("foo.bar", "Id")
+                        "foo.bar.Id"
                     )
                 )
             }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
index 7942588..325996b 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/RawQueryMethodProcessorTest.kt
@@ -19,6 +19,9 @@
 import COMMON
 import androidx.room.Dao
 import androidx.room.RawQuery
+import androidx.room.compiler.codegen.XClassName
+import androidx.room.compiler.codegen.XTypeName
+import androidx.room.compiler.codegen.asClassName
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
@@ -35,9 +38,6 @@
 import androidx.room.testing.context
 import androidx.room.vo.RawQueryMethod
 import androidx.sqlite.db.SupportSQLiteQuery
-import com.squareup.javapoet.ArrayTypeName
-import com.squareup.javapoet.ClassName
-import com.squareup.javapoet.TypeName
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Test
@@ -62,8 +62,8 @@
                 )
             )
             assertThat(
-                query.returnType.typeName,
-                `is`(ArrayTypeName.of(TypeName.INT) as TypeName)
+                query.returnType.asTypeName(),
+                `is`(XTypeName.getArrayName(XTypeName.PRIMITIVE_INT))
             )
         }
     }
@@ -179,7 +179,7 @@
 
     @Test
     fun pojo() {
-        val pojo: TypeName = ClassName.get("foo.bar.MyClass", "MyPojo")
+        val pojo = XClassName.get("foo.bar.MyClass", "MyPojo")
         singleQueryMethod(
             """
                 public class MyPojo {
@@ -201,7 +201,7 @@
                     )
                 )
             )
-            assertThat(query.returnType.typeName, `is`(pojo))
+            assertThat(query.returnType.asTypeName(), `is`(pojo))
             assertThat(query.observedTableNames, `is`(emptySet()))
         }
     }
@@ -329,13 +329,13 @@
     fun observed_notAnEntity() {
         singleQueryMethod(
             """
-                @RawQuery(observedEntities = {${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class})
+                @RawQuery(observedEntities = {${COMMON.NOT_AN_ENTITY_TYPE_NAME.canonicalName}.class})
                 abstract public int[] foo(SupportSQLiteQuery query);
                 """
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    ProcessorErrors.rawQueryBadEntity(COMMON.NOT_AN_ENTITY_TYPE_NAME)
+                    ProcessorErrors.rawQueryBadEntity(COMMON.NOT_AN_ENTITY_TYPE_NAME.canonicalName)
                 )
             }
         }
@@ -424,7 +424,7 @@
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     ProcessorErrors.valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "String")
+                        String::class.asClassName().canonicalName
                     )
                 )
             }
@@ -442,7 +442,7 @@
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     ProcessorErrors.valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "String")
+                        String::class.asClassName().canonicalName
                     )
                 )
             }
@@ -460,7 +460,7 @@
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     ProcessorErrors.valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "String")
+                        String::class.asClassName().canonicalName
                     )
                 )
             }
@@ -477,9 +477,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    ProcessorErrors.valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "Long")
-                    )
+                    ProcessorErrors.valueMayNeedMapInfo(XTypeName.BOXED_LONG.canonicalName)
                 )
             }
         }
@@ -495,9 +493,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    ProcessorErrors.valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "Long")
-                    )
+                    ProcessorErrors.valueMayNeedMapInfo(XTypeName.BOXED_LONG.canonicalName)
                 )
             }
         }
@@ -513,9 +509,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    ProcessorErrors.valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "Long")
-                    )
+                    ProcessorErrors.valueMayNeedMapInfo(XTypeName.BOXED_LONG.canonicalName)
                 )
             }
         }
@@ -532,9 +526,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    ProcessorErrors.keyMayNeedMapInfo(
-                        ClassName.get("java.util", "Date")
-                    )
+                    ProcessorErrors.keyMayNeedMapInfo("java.util.Date")
                 )
             }
         }
@@ -551,9 +543,7 @@
         ) { _, invocation ->
             invocation.assertCompilationResult {
                 hasErrorContaining(
-                    ProcessorErrors.valueMayNeedMapInfo(
-                        ClassName.get("java.util", "Date")
-                    )
+                    ProcessorErrors.valueMayNeedMapInfo("java.util.Date")
                 )
             }
         }
@@ -571,7 +561,7 @@
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     ProcessorErrors.valueMayNeedMapInfo(
-                        ClassName.get("java.lang", "String")
+                        String::class.asClassName().canonicalName
                     )
                 )
             }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
index 7a4b1ae..37534d1 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/TableEntityProcessorTest.kt
@@ -17,6 +17,8 @@
 package androidx.room.processor
 
 import COMMON
+import androidx.room.compiler.codegen.CodeLanguage
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compileFiles
@@ -33,7 +35,6 @@
 import androidx.room.vo.Pojo
 import androidx.room.vo.columnNames
 import com.google.common.truth.Truth.assertThat
-import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.TypeName
 import org.hamcrest.CoreMatchers.hasItems
 import org.hamcrest.CoreMatchers.`is`
@@ -56,7 +57,10 @@
                 public void setId(int id) { this.id = id; }
             """
         ) { entity, invocation ->
-            assertThat(entity.type.typeName.toString(), `is`("foo.bar.MyEntity"))
+            assertThat(
+                entity.type.asTypeName().toString(CodeLanguage.JAVA),
+                `is`("foo.bar.MyEntity")
+            )
             assertThat(entity.fields.size, `is`(1))
             val field = entity.fields.first()
             val intType = invocation.processingEnv.requireType(TypeName.INT)
@@ -281,16 +285,16 @@
             val cursorValueReader = idField.cursorValueReader
                 ?: throw AssertionError("must have a cursor value reader")
             assertThat(
-                cursorValueReader.typeMirror().typeName,
-                `is`(invocation.processingEnv.requireType(TypeName.INT).typeName)
+                cursorValueReader.typeMirror().asTypeName(),
+                `is`(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_INT).asTypeName())
             )
             invocation.assertCompilationResult {
                 hasWarningContaining(
                     ProcessorErrors.mismatchedSetter(
                         fieldName = "id",
-                        ownerType = ClassName.bestGuess("foo.bar.MyEntity"),
-                        setterType = TypeName.INT,
-                        fieldType = TypeName.INT.box()
+                        ownerType = "foo.bar.MyEntity",
+                        setterType = "int",
+                        fieldType = XTypeName.BOXED_INT.canonicalName
                     )
                 )
             }
@@ -311,8 +315,8 @@
             val statementBinder = idField.statementBinder
                 ?: throw AssertionError("must have a statement binder")
             assertThat(
-                statementBinder.typeMirror().typeName,
-                `is`(invocation.processingEnv.requireType(TypeName.INT).typeName)
+                statementBinder.typeMirror().asTypeName(),
+                `is`(invocation.processingEnv.requireType(XTypeName.PRIMITIVE_INT).asTypeName())
             )
         }
     }
@@ -2045,7 +2049,7 @@
     fun foreignKey_invalidAction() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = "lastName",
                     childColumns = "name",
                     onDelete = 101
@@ -2105,7 +2109,7 @@
     fun foreignKey_notAnEntity() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.NOT_AN_ENTITY_TYPE_NAME}.class,
+                    entity = ${COMMON.NOT_AN_ENTITY_TYPE_NAME.canonicalName}.class,
                     parentColumns = "lastName",
                     childColumns = "name"
                 )}
@@ -2122,7 +2126,7 @@
             invocation.assertCompilationResult {
                 hasErrorContaining(
                     ProcessorErrors.foreignKeyNotAnEntity(
-                        COMMON.NOT_AN_ENTITY_TYPE_NAME.toString()
+                        COMMON.NOT_AN_ENTITY_TYPE_NAME.canonicalName
                     )
                 )
             }
@@ -2133,7 +2137,7 @@
     fun foreignKey_invalidChildColumn() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = "lastName",
                     childColumns = "namex"
                 )}
@@ -2161,7 +2165,7 @@
     fun foreignKey_columnCountMismatch() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = "lastName",
                     childColumns = {"name", "id"}
                 )}
@@ -2189,7 +2193,7 @@
     fun foreignKey_emptyChildColumns() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = "lastName",
                     childColumns = {}
                 )}
@@ -2213,7 +2217,7 @@
     fun foreignKey_emptyParentColumns() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = {},
                     childColumns = {"name"}
                 )}
@@ -2237,7 +2241,7 @@
     fun foreignKey_simple() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = "lastName",
                     childColumns = "name",
                     onDelete = ForeignKey.SET_NULL,
@@ -2269,7 +2273,7 @@
     fun foreignKey_dontDuplicationChildIndex_SingleColumn() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = "lastName",
                     childColumns = "name",
                     onDelete = ForeignKey.SET_NULL,
@@ -2297,7 +2301,7 @@
     fun foreignKey_dontDuplicationChildIndex_MultipleColumns() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = {"lastName", "name"},
                     childColumns = {"lName", "name"},
                     onDelete = ForeignKey.SET_NULL,
@@ -2327,7 +2331,7 @@
     fun foreignKey_dontDuplicationChildIndex_WhenCovered() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = {"lastName"},
                     childColumns = {"name"},
                     onDelete = ForeignKey.SET_NULL,
@@ -2357,7 +2361,7 @@
     fun foreignKey_warnMissingChildIndex() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = "lastName",
                     childColumns = "name",
                     onDelete = ForeignKey.SET_NULL,
@@ -2385,7 +2389,7 @@
     fun foreignKey_warnMissingChildrenIndex() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = {"lastName", "name"},
                     childColumns = {"lName", "name"}
                 )}
@@ -2418,7 +2422,7 @@
     fun foreignKey_dontIndexIfAlreadyPrimaryKey() {
         val annotation = mapOf(
             "foreignKeys" to """{@ForeignKey(
-                    entity = ${COMMON.USER_TYPE_NAME}.class,
+                    entity = ${COMMON.USER_TYPE_NAME.canonicalName}.class,
                     parentColumns = "lastName",
                     childColumns = "id",
                     onDelete = ForeignKey.SET_NULL,
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
index cb7c91a..145140c 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/autovalue/AutoValuePojoProcessorDelegateTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.processor.autovalue
 
+import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
@@ -26,21 +27,20 @@
 import androidx.room.testing.context
 import androidx.room.vo.Pojo
 import com.google.auto.value.processor.AutoValueProcessor
-import com.squareup.javapoet.ClassName
+import java.io.File
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.CoreMatchers.notNullValue
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import java.io.File
 
 @RunWith(JUnit4::class)
 class AutoValuePojoProcessorDelegateTest {
 
     companion object {
-        val MY_POJO: ClassName = ClassName.get("foo.bar", "MyPojo")
-        val AUTOVALUE_MY_POJO: ClassName = ClassName.get("foo.bar", "AutoValue_MyPojo")
+        val MY_POJO = XClassName.get("foo.bar", "MyPojo")
+        val AUTOVALUE_MY_POJO = XClassName.get("foo.bar", "AutoValue_MyPojo")
         val HEADER = """
             package foo.bar;
 
@@ -80,7 +80,7 @@
                 long getId() { return this.id; }
                 """
         ) { pojo, invocation ->
-            assertThat(pojo.type.typeName, `is`(MY_POJO))
+            assertThat(pojo.type.asTypeName(), `is`(MY_POJO))
             assertThat(pojo.fields.size, `is`(1))
             assertThat(pojo.constructor?.element, `is`(notNullValue()))
             invocation.assertCompilationResult {
@@ -94,7 +94,7 @@
         val libraryClasspath = compileFiles(
             sources = listOf(
                 Source.java(
-                    MY_POJO.toString(),
+                    MY_POJO.canonicalName,
                     """
                     $HEADER
                     @AutoValue.CopyAnnotations
@@ -275,8 +275,8 @@
         classpathFiles: List<File> = emptyList(),
         handler: (Pojo, XTestInvocation) -> Unit
     ) {
-        val pojoSource = Source.java(MY_POJO.toString(), pojoCode)
-        val autoValuePojoSource = Source.java(AUTOVALUE_MY_POJO.toString(), autoValuePojoCode)
+        val pojoSource = Source.java(MY_POJO.canonicalName, pojoCode)
+        val autoValuePojoSource = Source.java(AUTOVALUE_MY_POJO.canonicalName, autoValuePojoCode)
         val all: List<Source> = sources.toList() + pojoSource + autoValuePojoSource
         runProcessorTest(
             sources = all,
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
index 4b6fbf2..3bb81ea 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/Signatures.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/Signatures.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.solver
 
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.solver.types.CompositeTypeConverter
@@ -33,7 +34,8 @@
 }
 
 fun XType.toSignature() =
-    "$typeName${nullability.toSignature()}".substringAfterLast(".")
+    (asTypeName().toString(CodeLanguage.JAVA) + nullability.toSignature())
+        .substringAfterLast(".")
 
 fun TypeConverter.toSignature(): String {
     return when (this) {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
index 6809ade..285de55 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/solver/TypeAdapterStoreTest.kt
@@ -21,6 +21,7 @@
 import androidx.paging.PagingSource
 import androidx.room.Dao
 import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.toJavaPoet
 import androidx.room.compiler.processing.XProcessingEnv
 import androidx.room.compiler.processing.XRawType
@@ -185,12 +186,12 @@
             assertThat(adapter, instanceOf(CompositeAdapter::class.java))
             val composite = adapter as CompositeAdapter
             assertThat(
-                composite.intoStatementConverter?.from?.typeName,
-                `is`(TypeName.BOOLEAN.box())
+                composite.intoStatementConverter?.from?.asTypeName(),
+                `is`(XTypeName.BOXED_BOOLEAN.copy(nullable = true))
             )
             assertThat(
-                composite.columnTypeAdapter.out.typeName,
-                `is`(TypeName.INT.box())
+                composite.columnTypeAdapter.out.asTypeName(),
+                `is`(XTypeName.BOXED_INT.copy(nullable = true))
             )
         }
     }
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 a6c6499..150a7a3 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
@@ -16,6 +16,7 @@
 
 package androidx.room.solver
 
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.CustomConverterProcessor
@@ -136,7 +137,8 @@
     private fun TypeConverter.toSignature(): String {
         return when (this) {
             is CompositeTypeConverter -> "${conv1.toSignature()} : ${conv2.toSignature()}"
-            else -> "${from.typeName} -> ${to.typeName}"
+            else -> from.asTypeName().toString(CodeLanguage.JAVA) + " -> " +
+                to.asTypeName().toString(CodeLanguage.JAVA)
         }
     }
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt b/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
index 45d6eab..7aa7720 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/testing/test_util.kt
@@ -46,7 +46,6 @@
 import androidx.room.testing.context
 import androidx.room.verifier.DatabaseVerifier
 import androidx.room.writer.TypeWriter
-import com.squareup.javapoet.ClassName
 import java.io.File
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
@@ -79,7 +78,7 @@
         loadJavaCode("common/input/UserSummary.java", "foo.bar.UserSummary")
     }
     val USER_TYPE_NAME by lazy {
-        ClassName.get("foo.bar", "User")
+        XClassName.get("foo.bar", "User")
     }
     val BOOK by lazy {
         loadJavaCode("common/input/Book.java", "foo.bar.Book")
@@ -102,7 +101,7 @@
     }
 
     val NOT_AN_ENTITY_TYPE_NAME by lazy {
-        ClassName.get("foo.bar", "NotAnEntity")
+        XClassName.get("foo.bar", "NotAnEntity")
     }
 
     val MULTI_PKEY_ENTITY by lazy {
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
index 3525dfcd..e89a1d0 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DaoKotlinCodeGenTest.kt
@@ -1141,6 +1141,61 @@
     }
 
     @Test
+    fun queryResultAdapter_array() {
+        val testName = object {}.javaClass.enclosingMethod!!.name
+        val dbSource = Source.kotlin(
+            "MyDatabase.kt",
+            """
+            import androidx.room.*
+
+            @Database(entities = [MyEntity::class], version = 1, exportSchema = false)
+            abstract class MyDatabase : RoomDatabase() {
+                abstract fun getDao(): MyDao
+            }
+            """.trimIndent()
+        )
+        val src = Source.kotlin(
+            "MyDao.kt",
+            """
+            import androidx.room.*
+
+            @Dao
+            interface MyDao {
+              @Query("SELECT * FROM MyEntity")
+              fun queryOfArray(): Array<MyEntity>
+
+              @Query("SELECT * FROM MyEntity")
+              fun queryOfNullableEntityArray(): Array<MyEntity?>
+
+              @Query("SELECT pk FROM MyEntity")
+              fun queryOfArrayWithLong(): Array<Long>
+
+              @Query("SELECT pk FROM MyEntity")
+              fun queryOfArrayWithNullLong(): Array<Long?>
+
+              @Query("SELECT * FROM MyEntity")
+              fun queryOfLongArray(): LongArray
+
+              @Query("SELECT * FROM MyEntity")
+              fun queryOfShortArray(): ShortArray
+            }
+
+            @Entity
+            data class MyEntity(
+                @PrimaryKey
+                val pk: Int,
+                val other: String,
+                val other2: Long
+            )
+            """.trimIndent()
+        )
+        runTest(
+            sources = listOf(src, dbSource),
+            expectedFilePath = getTestGoldenPath(testName)
+        )
+    }
+
+    @Test
     fun abstractClassWithParam() {
         val testName = object {}.javaClass.enclosingMethod!!.name
         val src = Source.kotlin(
diff --git a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/AutoMigrationWithProvidedSpec.kt b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/AutoMigrationWithProvidedSpec.kt
index 559557c..db23290 100644
--- a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/AutoMigrationWithProvidedSpec.kt
+++ b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/AutoMigrationWithProvidedSpec.kt
@@ -8,7 +8,7 @@
 import kotlin.Unit
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDatabase_AutoMigration_1_2_Impl : Migration {
     private val callback: AutoMigrationSpec
 
diff --git a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithDefault.kt b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithDefault.kt
index 043d6ef..1a91afa 100644
--- a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithDefault.kt
+++ b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithDefault.kt
@@ -8,7 +8,7 @@
 import kotlin.Unit
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDatabase_AutoMigration_1_2_Impl : Migration {
     private val callback: AutoMigrationSpec = ValidAutoMigrationWithDefault()
 
diff --git a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithoutDefault.kt b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithoutDefault.kt
index 3d0ef8d..51d3943 100644
--- a/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithoutDefault.kt
+++ b/room/room-compiler/src/test/test-data/autoMigrationWriter/output/kotlin/ValidAutoMigrationWithoutDefault.kt
@@ -8,7 +8,7 @@
 import kotlin.Unit
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDatabase_AutoMigration_1_2_Impl : Migration {
     private val callback: AutoMigrationSpec = ValidAutoMigrationWithoutDefault()
 
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
index a8b3ea9..f60029c 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/javac/ComplexDao.java
@@ -265,14 +265,15 @@
         __db.assertNotSuspendingTransaction();
         final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
         try {
-            final int[] _result = new int[_cursor.getCount()];
+            final int[] _tmpResult = new int[_cursor.getCount()];
             int _index = 0;
-            while(_cursor.moveToNext()) {
+            while (_cursor.moveToNext()) {
                 final int _item_1;
                 _item_1 = _cursor.getInt(0);
-                _result[_index] = _item_1;
-                _index ++;
+                _tmpResult[_index] = _item_1;
+                _index++;
             }
+            final int[] _result = _tmpResult;
             return _result;
         } finally {
             _cursor.close();
diff --git a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
index c2c1f0d..784122f 100644
--- a/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
+++ b/room/room-compiler/src/test/test-data/daoWriter/output/ksp/ComplexDao.java
@@ -229,14 +229,15 @@
         __db.assertNotSuspendingTransaction();
         final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
         try {
-            final int[] _result = new int[_cursor.getCount()];
+            final int[] _tmpResult = new int[_cursor.getCount()];
             int _index = 0;
-            while(_cursor.moveToNext()) {
+            while (_cursor.moveToNext()) {
                 final int _item_1;
                 _item_1 = _cursor.getInt(0);
-                _result[_index] = _item_1;
-                _index ++;
+                _tmpResult[_index] = _item_1;
+                _index++;
             }
+            final int[] _result = _tmpResult;
             return _result;
         } finally {
             _cursor.close();
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/abstractClassWithParam.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/abstractClassWithParam.kt
index 52f9323..2d03d4c 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/abstractClassWithParam.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/abstractClassWithParam.kt
@@ -13,7 +13,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao(__db) {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/arrayParameterAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/arrayParameterAdapter.kt
index 83e8aee..c4c45a9 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/arrayParameterAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/arrayParameterAdapter.kt
@@ -18,7 +18,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/basicParameterAdapter_string.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/basicParameterAdapter_string.kt
index ebeb1fe..d4c0490 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/basicParameterAdapter_string.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/basicParameterAdapter_string.kt
@@ -13,7 +13,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/collectionParameterAdapter_string.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/collectionParameterAdapter_string.kt
index c2c2d0d..0e060b0 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/collectionParameterAdapter_string.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/collectionParameterAdapter_string.kt
@@ -17,7 +17,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutineResultBinder.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutineResultBinder.kt
index 629b4d5..2667374 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutineResultBinder.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutineResultBinder.kt
@@ -17,7 +17,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_propertyDao.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_propertyDao.kt
index 75cb1b2..18433a0 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_propertyDao.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_propertyDao.kt
@@ -24,7 +24,7 @@
 import kotlin.collections.Set
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDatabase_Impl : MyDatabase() {
     private val _myDao: Lazy<MyDao> = lazy { MyDao_Impl(this) }
 
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_simple.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_simple.kt
index e22f870..81825b4 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_simple.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_simple.kt
@@ -24,7 +24,7 @@
 import kotlin.collections.Set
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDatabase_Impl : MyDatabase() {
     private val _myDao: Lazy<MyDao> = lazy { MyDao_Impl(this) }
 
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_withFtsAndView.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_withFtsAndView.kt
index fa8e301..39245f4 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/database_withFtsAndView.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/database_withFtsAndView.kt
@@ -27,7 +27,7 @@
 import kotlin.collections.Set
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDatabase_Impl : MyDatabase() {
     private val _myDao: Lazy<MyDao> = lazy { MyDao_Impl(this) }
 
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_boxedPrimitiveBridge.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_boxedPrimitiveBridge.kt
index da53b2a..f760be1 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_boxedPrimitiveBridge.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_boxedPrimitiveBridge.kt
@@ -16,7 +16,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_defaultImplBridge.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_defaultImplBridge.kt
index 3e6fa5a..520f28d 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_defaultImplBridge.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/delegatingFunctions_defaultImplBridge.kt
@@ -14,7 +14,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
index 376ee20..ebc321a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/deleteOrUpdateMethodAdapter.kt
@@ -11,7 +11,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
index a14eefe..6d67be8 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/entityRowAdapter.kt
@@ -18,7 +18,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
index 8b0298b..4f533d9 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/insertOrUpsertMethodAdapter.kt
@@ -13,7 +13,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
index d647d87..1a20e69 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_boolean.kt
@@ -17,7 +17,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
index 259d8b0..92125d1 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_byteArray.kt
@@ -17,7 +17,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
index 418314d..7918554 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter.kt
@@ -16,7 +16,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
index d717c5e..c858bad 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_composite.kt
@@ -16,7 +16,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
index 792a573..ea32abb 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_nullAware.kt
@@ -16,7 +16,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
index 3e7273a..367594f 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_provided.kt
@@ -17,7 +17,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt
index ac5044e..4fa2454 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_customTypeConverter_upcast.kt
@@ -16,7 +16,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
index 0326a4c..1ff29ed 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_embedded.kt
@@ -17,7 +17,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
index 8eca87d..8d9e62a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_enum.kt
@@ -17,7 +17,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
index 7180121..9cedbb7 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_internalVisibility.kt
@@ -17,7 +17,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
index f1b3503..44bccbb 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives.kt
@@ -22,7 +22,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
index f1a820e..1704532f 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_primitives_nullable.kt
@@ -22,7 +22,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
index 4a47bc6..d376c52 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_string.kt
@@ -16,7 +16,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
index 5b24625..a1fa31b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_uuid.kt
@@ -19,7 +19,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
index ce7bf9f..5eaf820 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty.kt
@@ -16,7 +16,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
index e5e7baa..70a1366 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/pojoRowAdapter_variableProperty_java.kt
@@ -14,7 +14,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt
index cd5c218..adb37c5 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/preparedQueryAdapter.kt
@@ -12,7 +12,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_array.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_array.kt
new file mode 100644
index 0000000..1609b43
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_array.kt
@@ -0,0 +1,189 @@
+import android.database.Cursor
+import androidx.room.RoomDatabase
+import androidx.room.RoomSQLiteQuery
+import androidx.room.RoomSQLiteQuery.Companion.acquire
+import androidx.room.util.getColumnIndexOrThrow
+import androidx.room.util.query
+import java.lang.Class
+import javax.`annotation`.processing.Generated
+import kotlin.Array
+import kotlin.Int
+import kotlin.Long
+import kotlin.LongArray
+import kotlin.Short
+import kotlin.ShortArray
+import kotlin.String
+import kotlin.Suppress
+import kotlin.arrayOfNulls
+import kotlin.collections.List
+import kotlin.jvm.JvmStatic
+
+@Generated(value = ["androidx.room.RoomProcessor"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
+public class MyDao_Impl(
+    __db: RoomDatabase,
+) : MyDao {
+    private val __db: RoomDatabase
+    init {
+        this.__db = __db
+    }
+
+    public override fun queryOfArray(): Array<MyEntity> {
+        val _sql: String = "SELECT * FROM MyEntity"
+        val _statement: RoomSQLiteQuery = acquire(_sql, 0)
+        __db.assertNotSuspendingTransaction()
+        val _cursor: Cursor = query(__db, _statement, false, null)
+        try {
+            val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
+            val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
+            val _cursorIndexOfOther2: Int = getColumnIndexOrThrow(_cursor, "other2")
+            val _tmpResult: Array<MyEntity?> = arrayOfNulls<MyEntity>(_cursor.getCount())
+            var _index: Int = 0
+            while (_cursor.moveToNext()) {
+                val _item: MyEntity
+                val _tmpPk: Int
+                _tmpPk = _cursor.getInt(_cursorIndexOfPk)
+                val _tmpOther: String
+                _tmpOther = _cursor.getString(_cursorIndexOfOther)
+                val _tmpOther2: Long
+                _tmpOther2 = _cursor.getLong(_cursorIndexOfOther2)
+                _item = MyEntity(_tmpPk,_tmpOther,_tmpOther2)
+                _tmpResult[_index] = _item
+                _index++
+            }
+            val _result: Array<MyEntity> = (_tmpResult) as Array<MyEntity>
+            return _result
+        } finally {
+            _cursor.close()
+            _statement.release()
+        }
+    }
+
+    public override fun queryOfNullableEntityArray(): Array<MyEntity?> {
+        val _sql: String = "SELECT * FROM MyEntity"
+        val _statement: RoomSQLiteQuery = acquire(_sql, 0)
+        __db.assertNotSuspendingTransaction()
+        val _cursor: Cursor = query(__db, _statement, false, null)
+        try {
+            val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
+            val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
+            val _cursorIndexOfOther2: Int = getColumnIndexOrThrow(_cursor, "other2")
+            val _tmpResult: Array<MyEntity?> = arrayOfNulls<MyEntity?>(_cursor.getCount())
+            var _index: Int = 0
+            while (_cursor.moveToNext()) {
+                val _item: MyEntity?
+                val _tmpPk: Int
+                _tmpPk = _cursor.getInt(_cursorIndexOfPk)
+                val _tmpOther: String
+                _tmpOther = _cursor.getString(_cursorIndexOfOther)
+                val _tmpOther2: Long
+                _tmpOther2 = _cursor.getLong(_cursorIndexOfOther2)
+                _item = MyEntity(_tmpPk,_tmpOther,_tmpOther2)
+                _tmpResult[_index] = _item
+                _index++
+            }
+            val _result: Array<MyEntity?> = _tmpResult
+            return _result
+        } finally {
+            _cursor.close()
+            _statement.release()
+        }
+    }
+
+    public override fun queryOfArrayWithLong(): Array<Long> {
+        val _sql: String = "SELECT pk FROM MyEntity"
+        val _statement: RoomSQLiteQuery = acquire(_sql, 0)
+        __db.assertNotSuspendingTransaction()
+        val _cursor: Cursor = query(__db, _statement, false, null)
+        try {
+            val _tmpResult: Array<Long?> = arrayOfNulls<Long>(_cursor.getCount())
+            var _index: Int = 0
+            while (_cursor.moveToNext()) {
+                val _item: Long
+                _item = _cursor.getLong(0)
+                _tmpResult[_index] = _item
+                _index++
+            }
+            val _result: Array<Long> = (_tmpResult) as Array<Long>
+            return _result
+        } finally {
+            _cursor.close()
+            _statement.release()
+        }
+    }
+
+    public override fun queryOfArrayWithNullLong(): Array<Long?> {
+        val _sql: String = "SELECT pk FROM MyEntity"
+        val _statement: RoomSQLiteQuery = acquire(_sql, 0)
+        __db.assertNotSuspendingTransaction()
+        val _cursor: Cursor = query(__db, _statement, false, null)
+        try {
+            val _tmpResult: Array<Long?> = arrayOfNulls<Long?>(_cursor.getCount())
+            var _index: Int = 0
+            while (_cursor.moveToNext()) {
+                val _item: Long?
+                if (_cursor.isNull(0)) {
+                    _item = null
+                } else {
+                    _item = _cursor.getLong(0)
+                }
+                _tmpResult[_index] = _item
+                _index++
+            }
+            val _result: Array<Long?> = _tmpResult
+            return _result
+        } finally {
+            _cursor.close()
+            _statement.release()
+        }
+    }
+
+    public override fun queryOfLongArray(): LongArray {
+        val _sql: String = "SELECT * FROM MyEntity"
+        val _statement: RoomSQLiteQuery = acquire(_sql, 0)
+        __db.assertNotSuspendingTransaction()
+        val _cursor: Cursor = query(__db, _statement, false, null)
+        try {
+            val _tmpResult: Array<Long?> = arrayOfNulls<Long>(_cursor.getCount())
+            var _index: Int = 0
+            while (_cursor.moveToNext()) {
+                val _item: Long
+                _item = _cursor.getLong(0)
+                _tmpResult[_index] = _item
+                _index++
+            }
+            val _result: LongArray = ((_tmpResult) as Array<Long>).toLongArray()
+            return _result
+        } finally {
+            _cursor.close()
+            _statement.release()
+        }
+    }
+
+    public override fun queryOfShortArray(): ShortArray {
+        val _sql: String = "SELECT * FROM MyEntity"
+        val _statement: RoomSQLiteQuery = acquire(_sql, 0)
+        __db.assertNotSuspendingTransaction()
+        val _cursor: Cursor = query(__db, _statement, false, null)
+        try {
+            val _tmpResult: Array<Short?> = arrayOfNulls<Short>(_cursor.getCount())
+            var _index: Int = 0
+            while (_cursor.moveToNext()) {
+                val _item: Short
+                _item = _cursor.getShort(0)
+                _tmpResult[_index] = _item
+                _index++
+            }
+            val _result: ShortArray = ((_tmpResult) as Array<Short>).toShortArray()
+            return _result
+        } finally {
+            _cursor.close()
+            _statement.release()
+        }
+    }
+
+    public companion object {
+        @JvmStatic
+        public fun getRequiredConverters(): List<Class<*>> = emptyList()
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_list.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_list.kt
index 11a7cef..db92cd8 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_list.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_list.kt
@@ -15,7 +15,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map.kt
index 4d5d708..4f0d509 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map.kt
@@ -18,7 +18,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt
index 9217ac6..7548f6f 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt
@@ -22,7 +22,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/rawQuery.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/rawQuery.kt
index b42aaa4..036c89b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/rawQuery.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/rawQuery.kt
@@ -12,7 +12,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
index 4e5ad57..df4a093 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations.kt
@@ -23,7 +23,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
index 56c6b76..e92df63 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_arrayMap.kt
@@ -23,7 +23,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
index 498e033..5381501 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_byteBufferKey.kt
@@ -24,7 +24,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
index f1b082d7..36cd22f 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_longSparseArray.kt
@@ -22,7 +22,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
index a01fe75..7869fa2 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/relations_nullable.kt
@@ -23,7 +23,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt
index fcd26fa..3db73a3 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_abstractClass.kt
@@ -8,7 +8,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao() {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt
index 2975e78..790666a 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/transactionMethodAdapter_interface.kt
@@ -12,7 +12,7 @@
 import kotlin.jvm.JvmStatic
 
 @Generated(value = ["androidx.room.RoomProcessor"])
-@Suppress(names = ["unchecked", "deprecation"])
+@Suppress(names = ["UNCHECKED_CAST", "DEPRECATION"])
 public class MyDao_Impl(
     __db: RoomDatabase,
 ) : MyDao {
diff --git a/settings.gradle b/settings.gradle
index 31e3e3c..1c9f00b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -376,8 +376,8 @@
 includeProject(":appsearch:appsearch-local-storage", [BuildType.MAIN])
 includeProject(":appsearch:appsearch-platform-storage", [BuildType.MAIN])
 includeProject(":appsearch:appsearch-test-util", [BuildType.MAIN])
-includeProject(":arch:core:core-common", [BuildType.MAIN])
-includeProject(":arch:core:core-runtime", [BuildType.MAIN])
+includeProject(":arch:core:core-common", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
+includeProject(":arch:core:core-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
 includeProject(":arch:core:core-testing", [BuildType.MAIN])
 includeProject(":asynclayoutinflater:asynclayoutinflater", [BuildType.MAIN])
 includeProject(":asynclayoutinflater:asynclayoutinflater-appcompat", [BuildType.MAIN])
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
index 5459765..3f64139 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
@@ -28,6 +28,7 @@
 import android.view.KeyEvent;
 import android.widget.TextView;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -440,6 +441,8 @@
         validateMainActivityXml(xml);
     }
 
+
+    @FlakyTest(bugId = 259299647)
     @Test
     public void testWaitForWindowUpdate() {
         launchTestActivity(WaitTestActivity.class);
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java
index 94ef7cf..1231402 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/AccessibilityNodeInfoDumper.java
@@ -166,11 +166,7 @@
     }
 
     private static String safeCharSeqToString(CharSequence cs) {
-        if (cs == null)
-            return "";
-        else {
-            return stripInvalidXMLChars(cs);
-        }
+        return cs == null ? "" : stripInvalidXMLChars(cs);
     }
 
     private static String stripInvalidXMLChars(CharSequence cs) {
@@ -188,20 +184,16 @@
         for (int i = 0; i < cs.length(); i++) {
             ch = cs.charAt(i);
 
-            if((ch >= 0x1 && ch <= 0x8) || (ch >= 0xB && ch <= 0xC) || (ch >= 0xE && ch <= 0x1F) ||
-                    (ch >= 0x7F && ch <= 0x84) || (ch >= 0x86 && ch <= 0x9f) ||
-                    (ch >= 0xFDD0 && ch <= 0xFDDF) || (ch >= 0x1FFFE && ch <= 0x1FFFF) ||
-                    (ch >= 0x2FFFE && ch <= 0x2FFFF) || (ch >= 0x3FFFE && ch <= 0x3FFFF) ||
-                    (ch >= 0x4FFFE && ch <= 0x4FFFF) || (ch >= 0x5FFFE && ch <= 0x5FFFF) ||
-                    (ch >= 0x6FFFE && ch <= 0x6FFFF) || (ch >= 0x7FFFE && ch <= 0x7FFFF) ||
-                    (ch >= 0x8FFFE && ch <= 0x8FFFF) || (ch >= 0x9FFFE && ch <= 0x9FFFF) ||
-                    (ch >= 0xAFFFE && ch <= 0xAFFFF) || (ch >= 0xBFFFE && ch <= 0xBFFFF) ||
-                    (ch >= 0xCFFFE && ch <= 0xCFFFF) || (ch >= 0xDFFFE && ch <= 0xDFFFF) ||
-                    (ch >= 0xEFFFE && ch <= 0xEFFFF) || (ch >= 0xFFFFE && ch <= 0xFFFFF) ||
-                    (ch >= 0x10FFFE && ch <= 0x10FFFF))
+            if ((ch >= 0x1 && ch <= 0x8)
+                    || (ch >= 0xB && ch <= 0xC)
+                    || (ch >= 0xE && ch <= 0x1F)
+                    || (ch >= 0x7F && ch <= 0x84)
+                    || (ch >= 0x86 && ch <= 0x9F)
+                    || (ch >= 0xFDD0 && ch <= 0xFDDF)) {
                 ret.append(".");
-            else
+            } else {
                 ret.append(ch);
+            }
         }
         return ret.toString();
     }
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/BySelector.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/BySelector.java
index a441219..8ee007d 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/BySelector.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/BySelector.java
@@ -79,6 +79,9 @@
         mScrollable    = original.mScrollable;
         mSelected      = original.mSelected;
 
+        mMinDepth = original.mMinDepth;
+        mMaxDepth = original.mMaxDepth;
+
         for (BySelector childSelector : original.mChildSelectors) {
             mChildSelectors.add(new BySelector(childSelector));
         }
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
index df7192a..2881d27 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
@@ -135,11 +135,7 @@
                 mMask &= ~t.getEventType();
 
                 // Since we're waiting for all events to be matched at least once
-                if (mMask != 0)
-                    return false;
-
-                // all matched
-                return true;
+                return mMask == 0;
             }
 
             // no match yet
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java
index 451f493..780f82d 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject.java
@@ -895,10 +895,7 @@
      */
     public boolean waitForExists(long timeout) {
         Log.d(TAG, String.format("Waiting %dms for %s.", timeout, mUiSelector));
-        if(findAccessibilityNodeInfo(timeout) != null) {
-            return true;
-        }
-        return false;
+        return findAccessibilityNodeInfo(timeout) != null;
     }
 
     /**
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiScrollable.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiScrollable.java
index 8dcfbf5..1db9068 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiScrollable.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiScrollable.java
@@ -86,10 +86,7 @@
      * @return true if found else false
      */
     protected boolean exists(@NonNull UiSelector selector) {
-        if(getQueryController().findAccessibilityNodeInfo(selector) != null) {
-            return true;
-        }
-        return false;
+        return getQueryController().findAccessibilityNodeInfo(selector) != null;
     }
 
     /**
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiSelector.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiSelector.java
index 0ba67d6..5cb978a 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiSelector.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiSelector.java
@@ -109,9 +109,7 @@
      */
     @NonNull
     public UiSelector text(@NonNull String text) {
-        if (text == null) {
-            throw new IllegalArgumentException("text cannot be null");
-        }
+        checkNotNull(text, "text cannot be null");
         return buildSelector(SELECTOR_TEXT, text);
     }
 
@@ -127,9 +125,7 @@
      */
     @NonNull
     public UiSelector textMatches(@NonNull String regex) {
-        if (regex == null) {
-            throw new IllegalArgumentException("regex cannot be null");
-        }
+        checkNotNull(regex, "regex cannot be null");
         return buildSelector(SELECTOR_TEXT_REGEX, Pattern.compile(regex, Pattern.DOTALL));
     }
 
@@ -144,9 +140,7 @@
      */
     @NonNull
     public UiSelector textStartsWith(@NonNull String text) {
-        if (text == null) {
-            throw new IllegalArgumentException("text cannot be null");
-        }
+        checkNotNull(text, "text cannot be null");
         return buildSelector(SELECTOR_START_TEXT, text);
     }
 
@@ -161,9 +155,7 @@
      */
     @NonNull
     public UiSelector textContains(@NonNull String text) {
-        if (text == null) {
-            throw new IllegalArgumentException("text cannot be null");
-        }
+        checkNotNull(text, "text cannot be null");
         return buildSelector(SELECTOR_CONTAINS_TEXT, text);
     }
 
@@ -176,9 +168,7 @@
      */
     @NonNull
     public UiSelector className(@NonNull String className) {
-        if (className == null) {
-            throw new IllegalArgumentException("className cannot be null");
-        }
+        checkNotNull(className, "className cannot be null");
         return buildSelector(SELECTOR_CLASS, className);
     }
 
@@ -191,9 +181,7 @@
      */
     @NonNull
     public UiSelector classNameMatches(@NonNull String regex) {
-        if (regex == null) {
-            throw new IllegalArgumentException("regex cannot be null");
-        }
+        checkNotNull(regex, "regex cannot be null");
         return buildSelector(SELECTOR_CLASS_REGEX, Pattern.compile(regex));
     }
 
@@ -206,9 +194,7 @@
      */
     @NonNull
     public <T> UiSelector className(@NonNull Class<T> type) {
-        if (type == null) {
-            throw new IllegalArgumentException("type cannot be null");
-        }
+        checkNotNull(type, "type cannot be null");
         return buildSelector(SELECTOR_CLASS, type.getName());
     }
 
@@ -230,9 +216,7 @@
      */
     @NonNull
     public UiSelector description(@NonNull String desc) {
-        if (desc == null) {
-            throw new IllegalArgumentException("desc cannot be null");
-        }
+        checkNotNull(desc, "desc cannot be null");
         return buildSelector(SELECTOR_DESCRIPTION, desc);
     }
 
@@ -252,9 +236,7 @@
      */
     @NonNull
     public UiSelector descriptionMatches(@NonNull String regex) {
-        if (regex == null) {
-            throw new IllegalArgumentException("regex cannot be null");
-        }
+        checkNotNull(regex, "regex cannot be null");
         return buildSelector(SELECTOR_DESCRIPTION_REGEX, Pattern.compile(regex, Pattern.DOTALL));
     }
 
@@ -276,9 +258,7 @@
      */
     @NonNull
     public UiSelector descriptionStartsWith(@NonNull String desc) {
-        if (desc == null) {
-            throw new IllegalArgumentException("desc cannot be null");
-        }
+        checkNotNull(desc, "desc cannot be null");
         return buildSelector(SELECTOR_START_DESCRIPTION, desc);
     }
 
@@ -300,9 +280,7 @@
      */
     @NonNull
     public UiSelector descriptionContains(@NonNull String desc) {
-        if (desc == null) {
-            throw new IllegalArgumentException("desc cannot be null");
-        }
+        checkNotNull(desc, "desc cannot be null");
         return buildSelector(SELECTOR_CONTAINS_DESCRIPTION, desc);
     }
 
@@ -314,9 +292,7 @@
      */
     @NonNull
     public UiSelector resourceId(@NonNull String id) {
-        if (id == null) {
-            throw new IllegalArgumentException("id cannot be null");
-        }
+        checkNotNull(id, "id cannot be null");
         return buildSelector(SELECTOR_RESOURCE_ID, id);
     }
 
@@ -329,9 +305,7 @@
      */
     @NonNull
     public UiSelector resourceIdMatches(@NonNull String regex) {
-        if (regex == null) {
-            throw new IllegalArgumentException("regex cannot be null");
-        }
+        checkNotNull(regex, "regex cannot be null");
         return buildSelector(SELECTOR_RESOURCE_ID_REGEX, Pattern.compile(regex));
     }
 
@@ -563,9 +537,7 @@
      */
     @NonNull
     public UiSelector childSelector(@NonNull UiSelector selector) {
-        if (selector == null) {
-            throw new IllegalArgumentException("selector cannot be null");
-        }
+        checkNotNull(selector, "selector cannot be null");
         return buildSelector(SELECTOR_CHILD, selector);
     }
 
@@ -589,9 +561,7 @@
      */
     @NonNull
     public UiSelector fromParent(@NonNull UiSelector selector) {
-        if (selector == null) {
-            throw new IllegalArgumentException("selector cannot be null");
-        }
+        checkNotNull(selector, "selector cannot be null");
         return buildSelector(SELECTOR_PARENT, selector);
     }
 
@@ -604,9 +574,7 @@
      */
     @NonNull
     public UiSelector packageName(@NonNull String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("name cannot be null");
-        }
+        checkNotNull(name, "name cannot be null");
         return buildSelector(SELECTOR_PACKAGE_NAME, name);
     }
 
@@ -619,9 +587,7 @@
      */
     @NonNull
     public UiSelector packageNameMatches(@NonNull String regex) {
-        if (regex == null) {
-            throw new IllegalArgumentException("regex cannot be null");
-        }
+        checkNotNull(regex, "regex cannot be null");
         return buildSelector(SELECTOR_PACKAGE_NAME_REGEX, Pattern.compile(regex));
     }
 
@@ -880,39 +846,24 @@
      * @return true if is leaf.
      */
     boolean isLeaf() {
-        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0 &&
-                mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0) {
-            return true;
-        }
-        return false;
+        return mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0
+                && mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0;
     }
 
     boolean hasChildSelector() {
-        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) < 0) {
-            return false;
-        }
-        return true;
+        return mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CHILD) >= 0;
     }
 
     boolean hasPatternSelector() {
-        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PATTERN) < 0) {
-            return false;
-        }
-        return true;
+        return mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PATTERN) >= 0;
     }
 
     boolean hasContainerSelector() {
-        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CONTAINER) < 0) {
-            return false;
-        }
-        return true;
+        return mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_CONTAINER) >= 0;
     }
 
     boolean hasParentSelector() {
-        if (mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) < 0) {
-            return false;
-        }
-        return true;
+        return mSelectorAttributes.indexOfKey(UiSelector.SELECTOR_PARENT) >= 0;
     }
 
     /**
@@ -1071,4 +1022,11 @@
         builder.append("]");
         return builder.toString();
     }
+
+    private static <T> T checkNotNull(T value, @NonNull String message) {
+        if (value == null) {
+            throw new NullPointerException(message);
+        }
+        return value;
+    }
 }
diff --git a/tv/tv-foundation/api/current.txt b/tv/tv-foundation/api/current.txt
index 5bc200c..201bb55 100644
--- a/tv/tv-foundation/api/current.txt
+++ b/tv/tv-foundation/api/current.txt
@@ -73,7 +73,6 @@
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TvGridItemSpan {
     method public int getCurrentLineSpan();
-    property public final int currentLineSpan;
   }
 
   public sealed interface TvLazyGridItemInfo {
@@ -149,6 +148,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
@@ -261,6 +262,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
diff --git a/tv/tv-foundation/api/public_plus_experimental_current.txt b/tv/tv-foundation/api/public_plus_experimental_current.txt
index fe3724e..70eb94426 100644
--- a/tv/tv-foundation/api/public_plus_experimental_current.txt
+++ b/tv/tv-foundation/api/public_plus_experimental_current.txt
@@ -77,7 +77,7 @@
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TvGridItemSpan {
     method public int getCurrentLineSpan();
-    property public final int currentLineSpan;
+    property @androidx.compose.foundation.ExperimentalFoundationApi public final int currentLineSpan;
   }
 
   public sealed interface TvLazyGridItemInfo {
@@ -154,6 +154,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
@@ -267,6 +269,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
diff --git a/tv/tv-foundation/api/restricted_current.txt b/tv/tv-foundation/api/restricted_current.txt
index 5bc200c..201bb55 100644
--- a/tv/tv-foundation/api/restricted_current.txt
+++ b/tv/tv-foundation/api/restricted_current.txt
@@ -73,7 +73,6 @@
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TvGridItemSpan {
     method public int getCurrentLineSpan();
-    property public final int currentLineSpan;
   }
 
   public sealed interface TvLazyGridItemInfo {
@@ -149,6 +148,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
@@ -261,6 +262,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int firstVisibleItemIndex;
     property public final int firstVisibleItemScrollOffset;
     property public final androidx.compose.foundation.interaction.InteractionSource interactionSource;
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt
index 5e29562..336c09e 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridTest.kt
@@ -1058,6 +1058,39 @@
             assertThat(state.numMeasurePasses).isEqualTo(1)
         }
     }
+
+    @Test
+    fun fillingFullSize_nextItemIsNotComposed() {
+        val state = TvLazyGridState()
+        state.prefetchingEnabled = false
+        val itemSizePx = 5f
+        val itemSize = with(rule.density) { itemSizePx.toDp() }
+        rule.setContentWithTestViewConfiguration {
+            LazyGrid(
+                1,
+                Modifier
+                    .testTag(LazyGridTag)
+                    .mainAxisSize(itemSize),
+                state
+            ) {
+                items(3) { index ->
+                    Box(Modifier.size(itemSize).testTag("$index"))
+                }
+            }
+        }
+
+        repeat(3) { index ->
+            rule.onNodeWithTag("$index")
+                .assertIsDisplayed()
+            rule.onNodeWithTag("${index + 1}")
+                .assertDoesNotExist()
+            rule.runOnIdle {
+                runBlocking {
+                    state.scrollBy(itemSizePx)
+                }
+            }
+        }
+    }
 }
 
 internal fun IntegerSubject.isEqualTo(expected: Int, tolerance: Int) {
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyScrollTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyScrollTest.kt
index f7fd6c7..4441259 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyScrollTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyScrollTest.kt
@@ -38,6 +38,7 @@
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -243,6 +244,34 @@
         assertSpringAnimation(toIndex = 0, toOffset = 40, fromIndex = 8)
     }
 
+    @Test
+    fun canScrollForward() = runBlocking {
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+        assertThat(state.canScrollForward).isTrue()
+        assertThat(state.canScrollBackward).isFalse()
+    }
+
+    @Ignore // b/259588804
+    @Test
+    fun canScrollBackward() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(itemsCount)
+        }
+        assertThat(state.firstVisibleItemIndex).isEqualTo(itemsCount - 6)
+        assertThat(state.canScrollForward).isFalse()
+        assertThat(state.canScrollBackward).isTrue()
+    }
+
+    @Test
+    fun canScrollForwardAndBackward() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(10)
+        }
+        assertThat(state.firstVisibleItemIndex).isEqualTo(10)
+        assertThat(state.canScrollForward).isTrue()
+        assertThat(state.canScrollBackward).isTrue()
+    }
+
     private fun assertSpringAnimation(
         toIndex: Int,
         toOffset: Int = 0,
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/BaseLazyListTestWithOrientation.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
index d451964..194c128 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
@@ -75,6 +75,13 @@
             this.fillMaxHeight()
         }
 
+    fun TvLazyListItemScope.fillParentMaxMainAxis() =
+        if (vertical) {
+            Modifier.fillParentMaxHeight()
+        } else {
+            Modifier.fillParentMaxWidth()
+        }
+
     fun TvLazyListItemScope.fillParentMaxCrossAxis() =
         if (vertical) {
             Modifier.fillParentMaxWidth()
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyColumnTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyColumnTest.kt
index 45825f43..d9f8e64 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyColumnTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyColumnTest.kt
@@ -56,6 +56,7 @@
 import androidx.compose.ui.test.onNodeWithText
 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.tv.foundation.PivotOffsets
@@ -349,6 +350,7 @@
             .assertPositionInRootIsEqualTo(30.dp, 50.dp)
     }
 
+    @FlakyTest(bugId = 259297305)
     @Test
     fun removalWithMutableStateListOf() {
         val items = mutableStateListOf("1", "2", "3")
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
index 43150c1..2a8e3e2 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
@@ -101,9 +101,8 @@
 
         // Assert.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'contains' with 'containsExactly'.
-            assertThat(placedItems).contains(0)
-            assertThat(visibleItems).contains(0)
+            assertThat(placedItems).containsExactly(0)
+            assertThat(visibleItems).containsExactly(0)
         }
     }
 
@@ -122,9 +121,8 @@
 
         // Assert.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(0, 1)
-            assertThat(visibleItems).containsAtLeast(0, 1)
+            assertThat(placedItems).containsExactly(0, 1)
+            assertThat(visibleItems).containsExactly(0, 1)
         }
     }
 
@@ -143,9 +141,8 @@
 
         // Assert.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(0, 1, 2)
-            assertThat(visibleItems).containsAtLeast(0, 1, 2)
+            assertThat(placedItems).containsExactly(0, 1, 2)
+            assertThat(visibleItems).containsExactly(0, 1, 2)
         }
     }
 
@@ -185,13 +182,11 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that the beyond bounds items are present.
                 if (expectedExtraItemsBeforeVisibleBounds()) {
-                    // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                    assertThat(placedItems).containsAtLeast(4, 5, 6, 7)
-                    assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                    assertThat(placedItems).containsExactly(4, 5, 6, 7)
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
                 } else {
-                    // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                    assertThat(placedItems).containsAtLeast(5, 6, 7, 8)
-                    assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                    assertThat(placedItems).containsExactly(5, 6, 7, 8)
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
                 placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
@@ -202,9 +197,8 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(5, 6, 7)
-            assertThat(visibleItems).containsAtLeast(5, 6, 7)
+            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
 
@@ -250,13 +244,11 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                        assertThat(placedItems).containsAtLeast(3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                        assertThat(placedItems).containsExactly(3, 4, 5, 6, 7)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
                     } else {
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                        assertThat(placedItems).containsAtLeast(5, 6, 7, 8, 9)
-                        assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
                     placedItems.clear()
                     // Return true to stop the search.
@@ -267,9 +259,8 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(5, 6, 7)
-            assertThat(visibleItems).containsAtLeast(5, 6, 7)
+            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
 
@@ -316,13 +307,11 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                        assertThat(placedItems).containsAtLeast(0, 1, 2, 3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                        assertThat(placedItems).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
                     } else {
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                        assertThat(placedItems).containsAtLeast(5, 6, 7, 8, 9, 10)
-                        assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9, 10)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
                     placedItems.clear()
                     // Return true to end the search.
@@ -333,8 +322,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(5, 6, 7)
+            assertThat(placedItems).containsExactly(5, 6, 7)
         }
     }
 
@@ -364,11 +352,15 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 5 }
+                        .onPlaced { placedItems += index + 6 }
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
+        rule.runOnIdle {
+            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
+            placedItems.clear()
+        }
 
         // Act.
         rule.runOnUiThread {
@@ -377,18 +369,16 @@
                 when (beyondBoundsLayoutDirection) {
                     Left, Right, Above, Below -> {
                         assertThat(placedItems).containsExactlyElementsIn(visibleItems)
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                        assertThat(placedItems).containsAtLeast(5, 6, 7)
-                        assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                        assertThat(placedItems).containsExactly(5, 6, 7)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
                     Before, After -> {
-                        // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
                         if (expectedExtraItemsBeforeVisibleBounds()) {
-                            assertThat(placedItems).containsAtLeast(4, 5, 6, 7)
-                            assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                            assertThat(placedItems).containsExactly(4, 5, 6, 7)
+                            assertThat(visibleItems).containsExactly(5, 6, 7)
                         } else {
-                            assertThat(placedItems).containsAtLeast(5, 6, 7, 8)
-                            assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                            assertThat(placedItems).containsExactly(5, 6, 7, 8)
+                            assertThat(visibleItems).containsExactly(5, 6, 7)
                         }
                     }
                 }
@@ -408,9 +398,8 @@
                     assertThat(beyondBoundsLayoutCount).isEqualTo(1)
 
                     // Assert that the beyond bounds items are removed.
-                    // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-                    assertThat(placedItems).containsAtLeast(5, 6, 7)
-                    assertThat(visibleItems).containsAtLeast(5, 6, 7)
+                    assertThat(placedItems).containsExactly(5, 6, 7)
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
                 else -> error("Unsupported BeyondBoundsLayoutDirection")
             }
@@ -466,9 +455,8 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            // TODO(b/228100623): Replace 'containsAtLeast' with 'containsExactly'.
-            assertThat(placedItems).containsAtLeast(5, 6, 7)
-            assertThat(visibleItems).containsAtLeast(5, 6, 7)
+            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
 
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListTest.kt
index 6e446cb..25fdd03 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListTest.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.border
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
@@ -1758,6 +1759,67 @@
             .assertCrossAxisSizeIsEqualTo(0.dp)
     }
 
+    @Test
+    fun fillingFullSize_nextItemIsNotComposed() {
+        val state = TvLazyListState()
+        state.prefetchingEnabled = false
+        val itemSizePx = 5f
+        val itemSize = with(rule.density) { itemSizePx.toDp() }
+        rule.setContentWithTestViewConfiguration {
+            LazyColumnOrRow(
+                Modifier
+                    .testTag(LazyListTag)
+                    .mainAxisSize(itemSize),
+                state = state
+            ) {
+                items(3) { index ->
+                    Box(fillParentMaxMainAxis().crossAxisSize(1.dp).testTag("$index"))
+                }
+            }
+        }
+
+        repeat(3) { index ->
+            rule.onNodeWithTag("$index")
+                .assertIsDisplayed()
+            rule.onNodeWithTag("${index + 1}")
+                .assertDoesNotExist()
+            rule.runOnIdle {
+                runBlocking {
+                    state.scrollBy(itemSizePx)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun fillingFullSize_crossAxisSizeOfVisibleItemIsUsed() {
+        val state = TvLazyListState()
+        val itemSizePx = 5f
+        val itemSize = with(rule.density) { itemSizePx.toDp() }
+        rule.setContentWithTestViewConfiguration {
+            LazyColumnOrRow(
+                Modifier
+                    .testTag(LazyListTag)
+                    .mainAxisSize(itemSize),
+                state = state
+            ) {
+                items(5) { index ->
+                    Box(fillParentMaxMainAxis().crossAxisSize(index.dp))
+                }
+            }
+        }
+
+        repeat(5) { index ->
+            rule.onNodeWithTag(LazyListTag)
+                .assertCrossAxisSizeIsEqualTo(index.dp)
+            rule.runOnIdle {
+                runBlocking {
+                    state.scrollBy(itemSizePx)
+                }
+            }
+        }
+    }
+
     // ********************* END OF TESTS *********************
     // Helper functions, etc. live below here
 
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyScrollTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyScrollTest.kt
index f5b44f4..e533a1e 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyScrollTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyScrollTest.kt
@@ -239,6 +239,33 @@
         assertSpringAnimation(toIndex = 0, toOffset = 20, fromIndex = 8)
     }
 
+    @Test
+    fun canScrollForward() = runBlocking {
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+        assertThat(state.canScrollForward).isTrue()
+        assertThat(state.canScrollBackward).isFalse()
+    }
+
+    @Test
+    fun canScrollBackward() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(itemsCount)
+        }
+        assertThat(state.firstVisibleItemIndex).isEqualTo(itemsCount - 3)
+        assertThat(state.canScrollForward).isFalse()
+        assertThat(state.canScrollBackward).isTrue()
+    }
+
+    @Test
+    fun canScrollForwardAndBackward() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(1)
+        }
+        assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        assertThat(state.canScrollForward).isTrue()
+        assertThat(state.canScrollBackward).isTrue()
+    }
+
     private fun assertSpringAnimation(
         toIndex: Int,
         toOffset: Int = 0,
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt
index d50c7ec..03003be 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt
@@ -138,7 +138,11 @@
         // then composing visible lines forward until we fill the whole viewport.
         // we want to have at least one line in visibleItems even if in fact all the items are
         // offscreen, this can happen if the content padding is larger than the available size.
-        while (currentMainAxisOffset <= maxMainAxis || visibleLines.isEmpty()) {
+        while (index.value < itemsCount &&
+            (currentMainAxisOffset < maxMainAxis ||
+                currentMainAxisOffset <= 0 || // filling beforeContentPadding area
+                visibleLines.isEmpty())
+        ) {
             val measuredLine = measuredLineProvider.getAndMeasure(index)
             if (measuredLine.isEmpty()) {
                 --index
@@ -250,7 +254,7 @@
         return TvLazyGridMeasureResult(
             firstVisibleLine = firstLine,
             firstVisibleLineScrollOffset = currentFirstLineScrollOffset,
-            canScrollForward = currentMainAxisOffset > maxOffset,
+            canScrollForward = index.value < itemsCount || currentMainAxisOffset > maxOffset,
             consumedScroll = consumedScroll,
             measureResult = layout(layoutWidth, layoutHeight) {
                 positionedItems.fastForEach { it.place(this) }
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridState.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridState.kt
index 42f4e83..15f05c7 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridState.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/TvLazyGridState.kt
@@ -258,8 +258,9 @@
     override val isScrollInProgress: Boolean
         get() = scrollableState.isScrollInProgress
 
-    private var canScrollBackward: Boolean = false
-    internal var canScrollForward: Boolean = false
+    override var canScrollForward: Boolean by mutableStateOf(false)
+        private set
+    override var canScrollBackward: Boolean by mutableStateOf(false)
         private set
 
     // TODO: Coroutine scrolling APIs will allow this to be private again once we have more
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt
index 13dfdce..856d77b 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt
@@ -150,8 +150,10 @@
         // then composing visible items forward until we fill the whole viewport.
         // we want to have at least one item in visibleItems even if in fact all the items are
         // offscreen, this can happen if the content padding is larger than the available size.
-        while ((currentMainAxisOffset <= maxMainAxis || visibleItems.isEmpty()) &&
-            index.value < itemsCount
+        while (index.value < itemsCount &&
+            (currentMainAxisOffset < maxMainAxis ||
+                currentMainAxisOffset <= 0 || // filling beforeContentPadding area
+                visibleItems.isEmpty())
         ) {
             val measuredItem = itemProvider.getAndMeasure(index)
             currentMainAxisOffset += measuredItem.sizeWithSpacings
@@ -301,7 +303,7 @@
         return LazyListMeasureResult(
             firstVisibleItem = firstItem,
             firstVisibleItemScrollOffset = currentFirstItemScrollOffset,
-            canScrollForward = currentMainAxisOffset > maxOffset,
+            canScrollForward = index.value < itemsCount || currentMainAxisOffset > maxOffset,
             consumedScroll = consumedScroll,
             measureResult = layout(layoutWidth, layoutHeight) {
                 positionedItems.fastForEach {
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListState.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListState.kt
index 6330923..2f0cc8c 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListState.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListState.kt
@@ -261,8 +261,9 @@
     override val isScrollInProgress: Boolean
         get() = scrollableState.isScrollInProgress
 
-    private var canScrollBackward: Boolean = false
-    internal var canScrollForward: Boolean = false
+    override var canScrollForward: Boolean by mutableStateOf(false)
+        private set
+    override var canScrollBackward: Boolean by mutableStateOf(false)
         private set
 
     // TODO: Coroutine scrolling APIs will allow this to be private again once we have more
diff --git a/tv/tv-material/samples/src/main/java/androidx/tv/tvmaterial/samples/App.kt b/tv/tv-material/samples/src/main/java/androidx/tv/tvmaterial/samples/App.kt
index 773c124..1e448a1 100644
--- a/tv/tv-material/samples/src/main/java/androidx/tv/tvmaterial/samples/App.kt
+++ b/tv/tv-material/samples/src/main/java/androidx/tv/tvmaterial/samples/App.kt
@@ -29,18 +29,13 @@
 
 @Composable
 fun App() {
-    var selectedTab by remember { mutableStateOf(navigationMap[Navigation.FeaturedCarousel]) }
+    var selectedTab by remember { mutableStateOf(Navigation.FeaturedCarousel) }
 
     Column(
         modifier = Modifier.padding(20.dp),
         verticalArrangement = Arrangement.spacedBy(20.dp),
     ) {
         TopNavigation(updateSelectedTab = { selectedTab = it })
-        when (reverseNavigationMap[selectedTab]) {
-            Navigation.FeaturedCarousel -> FeaturedCarousel()
-            Navigation.ImmersiveList -> SampleImmersiveList()
-            Navigation.LazyRowsAndColumns -> LazyRowsAndColumns()
-            else -> { }
-        }
+        selectedTab.action.invoke()
     }
 }
\ No newline at end of file
diff --git a/tv/tv-material/samples/src/main/java/androidx/tv/tvmaterial/samples/TopNavigation.kt b/tv/tv-material/samples/src/main/java/androidx/tv/tvmaterial/samples/TopNavigation.kt
index 8cee8db..9cda913 100644
--- a/tv/tv-material/samples/src/main/java/androidx/tv/tvmaterial/samples/TopNavigation.kt
+++ b/tv/tv-material/samples/src/main/java/androidx/tv/tvmaterial/samples/TopNavigation.kt
@@ -35,26 +35,18 @@
 import androidx.tv.material.TabRow
 import androidx.tv.material.TabRowDefaults
 
-enum class Navigation {
-  FeaturedCarousel,
-  ImmersiveList,
-  LazyRowsAndColumns,
+enum class Navigation(val displayName: String, val action: @Composable () -> Unit) {
+  LazyRowsAndColumns("Lazy Rows and Columns", { LazyRowsAndColumns() }),
+  FeaturedCarousel("Featured Carousel", { FeaturedCarousel() }),
+  ImmersiveList("Immersive List", { SampleImmersiveList() }),
 }
 
-val navigationMap =
-  hashMapOf(
-    Navigation.FeaturedCarousel to "Featured Carousel",
-    Navigation.ImmersiveList to "Immersive List",
-    Navigation.LazyRowsAndColumns to "Lazy Rows and Columns",
-  )
-val reverseNavigationMap = navigationMap.entries.associate { it.value to it.key }
-
 @Composable
 internal fun TopNavigation(
-  updateSelectedTab: (String) -> Unit = {},
+  updateSelectedTab: (Navigation) -> Unit = {},
 ) {
   var selectedTabIndex by remember { mutableStateOf(0) }
-  val tabs = navigationMap.entries.map { it.value }
+  val tabs = Navigation.values().map { it.displayName }
 
   // Pill indicator
   PillIndicatorTabRow(
@@ -63,7 +55,7 @@
     updateSelectedTab = { selectedTabIndex = it }
   )
 
-  LaunchedEffect(selectedTabIndex) { updateSelectedTab(tabs[selectedTabIndex]) }
+  LaunchedEffect(selectedTabIndex) { updateSelectedTab(Navigation.values()[selectedTabIndex]) }
 }
 
 /**
diff --git a/wear/compose/compose-material/api/current.txt b/wear/compose/compose-material/api/current.txt
index 7c9473c..c87b5b0 100644
--- a/wear/compose/compose-material/api/current.txt
+++ b/wear/compose/compose-material/api/current.txt
@@ -288,6 +288,8 @@
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToOption(int index, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void setNumberOfOptions(int);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public boolean isScrollInProgress;
     property public final int numberOfOptions;
     property public final boolean repeatItems;
@@ -472,6 +474,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int centerItemIndex;
     property public final int centerItemScrollOffset;
     property public boolean isScrollInProgress;
diff --git a/wear/compose/compose-material/api/public_plus_experimental_current.txt b/wear/compose/compose-material/api/public_plus_experimental_current.txt
index 4970d54..da6559b 100644
--- a/wear/compose/compose-material/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-material/api/public_plus_experimental_current.txt
@@ -304,6 +304,8 @@
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToOption(int index, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void setNumberOfOptions(int);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public boolean isScrollInProgress;
     property public final int numberOfOptions;
     property public final boolean repeatItems;
@@ -520,6 +522,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int centerItemIndex;
     property public final int centerItemScrollOffset;
     property public boolean isScrollInProgress;
@@ -682,12 +686,12 @@
     method @androidx.wear.compose.material.ExperimentalWearMaterialApi public final suspend Object? performFling(float velocity, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @androidx.wear.compose.material.ExperimentalWearMaterialApi public final suspend Object? snapTo(T? targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public final T! currentValue;
-    property public final float direction;
+    property @androidx.wear.compose.material.ExperimentalWearMaterialApi public final float direction;
     property public final boolean isAnimationRunning;
-    property public final androidx.compose.runtime.State<java.lang.Float> offset;
-    property public final androidx.compose.runtime.State<java.lang.Float> overflow;
-    property public final androidx.wear.compose.material.SwipeProgress<T> progress;
-    property public final T! targetValue;
+    property @androidx.wear.compose.material.ExperimentalWearMaterialApi public final androidx.compose.runtime.State<java.lang.Float> offset;
+    property @androidx.wear.compose.material.ExperimentalWearMaterialApi public final androidx.compose.runtime.State<java.lang.Float> overflow;
+    property @androidx.wear.compose.material.ExperimentalWearMaterialApi public final androidx.wear.compose.material.SwipeProgress<T> progress;
+    property @androidx.wear.compose.material.ExperimentalWearMaterialApi public final T! targetValue;
     field public static final androidx.wear.compose.material.SwipeableState.Companion Companion;
   }
 
diff --git a/wear/compose/compose-material/api/restricted_current.txt b/wear/compose/compose-material/api/restricted_current.txt
index 7c9473c..c87b5b0 100644
--- a/wear/compose/compose-material/api/restricted_current.txt
+++ b/wear/compose/compose-material/api/restricted_current.txt
@@ -288,6 +288,8 @@
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToOption(int index, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void setNumberOfOptions(int);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public boolean isScrollInProgress;
     property public final int numberOfOptions;
     property public final boolean repeatItems;
@@ -472,6 +474,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
     property public final int centerItemIndex;
     property public final int centerItemScrollOffset;
     property public boolean isScrollInProgress;
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt
index f2d2d66..3434c10 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt
@@ -456,6 +456,12 @@
     public override val isScrollInProgress: Boolean
         get() = scalingLazyListState.isScrollInProgress
 
+    override val canScrollForward: Boolean
+        get() = scalingLazyListState.canScrollForward
+
+    override val canScrollBackward: Boolean
+        get() = scalingLazyListState.canScrollBackward
+
     private fun verifyNumberOfOptions(numberOfOptions: Int) {
         require(numberOfOptions > 0) { "The picker should have at least one item." }
         require(numberOfOptions < LARGE_NUMBER_OF_ITEMS / 3) {
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListState.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListState.kt
index 123c967..a8ef4a8 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListState.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListState.kt
@@ -403,6 +403,11 @@
         lazyListState.scroll(scrollPriority = scrollPriority, block = block)
     }
 
+    override val canScrollForward: Boolean
+        get() = lazyListState.canScrollForward
+
+    override val canScrollBackward: Boolean
+        get() = lazyListState.canScrollBackward
     /**
      * Instantly brings the item at [index] to the center of the viewport and positions it based on
      * the [anchorType] and applies the [scrollOffset] pixels.
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/SwipeToDismissBox.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/SwipeToDismissBox.kt
index 7b38d00..84af177e 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/SwipeToDismissBox.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/SwipeToDismissBox.kt
@@ -17,7 +17,6 @@
 package androidx.wear.compose.material
 
 import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.CubicBezierEasing
 import androidx.compose.animation.core.LinearOutSlowInEasing
 import androidx.compose.animation.core.TweenSpec
 import androidx.compose.foundation.background
@@ -154,12 +153,7 @@
 
                 val translationX = if (squeezeMode) squeezeOffset else slideOffset
 
-                val backgroundAlpha =
-                    lerp(
-                        MAX_BACKGROUND_SCRIM_ALPHA,
-                        MIN_BACKGROUND_SCRIM_ALPHA,
-                        BACKGROUND_SCRIM_EASING.transform(progress)
-                    )
+                val backgroundAlpha = MAX_BACKGROUND_SCRIM_ALPHA * (1 - progress)
                 val contentScrimAlpha = min(MAX_CONTENT_SCRIM_ALPHA, progress / 2f)
 
                 Modifiers(
@@ -590,8 +584,6 @@
 private const val SCALE_MAX = 1f
 private const val SCALE_MIN = 0.7f
 private const val MAX_CONTENT_SCRIM_ALPHA = 0.3f
-private const val MAX_BACKGROUND_SCRIM_ALPHA = 0.75f
-private const val MIN_BACKGROUND_SCRIM_ALPHA = 0f
-private val BACKGROUND_SCRIM_EASING = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)
+private const val MAX_BACKGROUND_SCRIM_ALPHA = 0.5f
 private val SWIPE_TO_DISMISS_BOX_ANIMATION_SPEC =
     TweenSpec<Float>(200, 0, LinearOutSlowInEasing)
diff --git a/wear/watchface/watchface-complications-data/api/current.txt b/wear/watchface/watchface-complications-data/api/current.txt
index 2f7342b..ff7ac43 100644
--- a/wear/watchface/watchface-complications-data/api/current.txt
+++ b/wear/watchface/watchface-complications-data/api/current.txt
@@ -86,6 +86,9 @@
   public final class DataKt {
   }
 
+  public final class DynamicFloatKt {
+  }
+
   public final class EmptyComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     ctor public EmptyComplicationData();
     field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
diff --git a/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt b/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
index 565b9cd..c92a5a5 100644
--- a/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
+++ b/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
@@ -89,6 +89,14 @@
   public final class DataKt {
   }
 
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public abstract class DynamicFloat {
+    ctor public DynamicFloat();
+    method public abstract byte[] asByteArray();
+  }
+
+  public final class DynamicFloatKt {
+  }
+
   public final class EmptyComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     ctor public EmptyComplicationData();
     field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
@@ -97,6 +105,7 @@
   @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class GoalProgressComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ColorRamp? getColorRamp();
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
+    method @androidx.wear.watchface.complications.data.ComplicationExperimental public androidx.wear.watchface.complications.data.DynamicFloat? getDynamicValue();
     method public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage();
     method public androidx.wear.watchface.complications.data.SmallImage? getSmallImage();
     method public float getTargetValue();
@@ -105,6 +114,7 @@
     method public float getValue();
     property public final androidx.wear.watchface.complications.data.ColorRamp? colorRamp;
     property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
+    property @androidx.wear.watchface.complications.data.ComplicationExperimental public final androidx.wear.watchface.complications.data.DynamicFloat? dynamicValue;
     property public final androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage;
     property public final androidx.wear.watchface.complications.data.SmallImage? smallImage;
     property public final float targetValue;
@@ -117,6 +127,7 @@
 
   @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public static final class GoalProgressComplicationData.Builder {
     ctor public GoalProgressComplicationData.Builder(float value, float targetValue, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    ctor @androidx.wear.watchface.complications.data.ComplicationExperimental public GoalProgressComplicationData.Builder(androidx.wear.watchface.complications.data.DynamicFloat dynamicValue, float targetValue, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
     method public androidx.wear.watchface.complications.data.GoalProgressComplicationData build();
     method public androidx.wear.watchface.complications.data.GoalProgressComplicationData.Builder setColorRamp(androidx.wear.watchface.complications.data.ColorRamp? colorRamp);
     method public final T setDataSource(android.content.ComponentName? dataSource);
@@ -265,6 +276,7 @@
   public final class RangedValueComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ColorRamp? getColorRamp();
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
+    method @androidx.wear.watchface.complications.data.ComplicationExperimental public androidx.wear.watchface.complications.data.DynamicFloat? getDynamicValue();
     method public float getMax();
     method public float getMin();
     method public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage();
@@ -275,6 +287,7 @@
     method public int getValueType();
     property public final androidx.wear.watchface.complications.data.ColorRamp? colorRamp;
     property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
+    property @androidx.wear.watchface.complications.data.ComplicationExperimental public final androidx.wear.watchface.complications.data.DynamicFloat? dynamicValue;
     property public final float max;
     property public final float min;
     property public final androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage;
@@ -292,6 +305,7 @@
 
   public static final class RangedValueComplicationData.Builder {
     ctor public RangedValueComplicationData.Builder(float value, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
+    ctor @androidx.wear.watchface.complications.data.ComplicationExperimental public RangedValueComplicationData.Builder(androidx.wear.watchface.complications.data.DynamicFloat dynamicValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
     method public androidx.wear.watchface.complications.data.RangedValueComplicationData build();
     method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setColorRamp(androidx.wear.watchface.complications.data.ColorRamp? colorRamp);
     method public final T setDataSource(android.content.ComponentName? dataSource);
diff --git a/wear/watchface/watchface-complications-data/api/restricted_current.txt b/wear/watchface/watchface-complications-data/api/restricted_current.txt
index 95ce87f..db4540b 100644
--- a/wear/watchface/watchface-complications-data/api/restricted_current.txt
+++ b/wear/watchface/watchface-complications-data/api/restricted_current.txt
@@ -86,6 +86,9 @@
   public final class DataKt {
   }
 
+  public final class DynamicFloatKt {
+  }
+
   public final class EmptyComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     ctor public EmptyComplicationData();
     field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt
index 34de840..19f21c4 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt
@@ -13,6 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+@file:OptIn(ComplicationExperimental::class)
+
 package android.support.wearable.complications
 
 import android.annotation.SuppressLint
@@ -31,8 +34,11 @@
 import androidx.annotation.RestrictTo
 import androidx.wear.watchface.complications.data.ComplicationDisplayPolicies
 import androidx.wear.watchface.complications.data.ComplicationDisplayPolicy
+import androidx.wear.watchface.complications.data.ComplicationExperimental
 import androidx.wear.watchface.complications.data.ComplicationPersistencePolicies
 import androidx.wear.watchface.complications.data.ComplicationPersistencePolicy
+import androidx.wear.watchface.complications.data.DynamicFloat
+import androidx.wear.watchface.complications.data.toDynamicFloat
 import java.io.IOException
 import java.io.InvalidObjectException
 import java.io.ObjectInputStream
@@ -171,6 +177,11 @@
             if (isFieldValidForType(FIELD_VALUE, type)) {
                 oos.writeFloat(complicationData.rangedValue)
             }
+            if (isFieldValidForType(FIELD_DYNAMIC_VALUE, type)) {
+                oos.writeNullable(complicationData.rangedDynamicValue) {
+                    oos.writeByteArray(it.asByteArray())
+                }
+            }
             if (isFieldValidForType(FIELD_VALUE_TYPE, type)) {
                 oos.writeInt(complicationData.rangedValueType)
             }
@@ -184,49 +195,16 @@
                 oos.writeFloat(complicationData.targetValue)
             }
             if (isFieldValidForType(FIELD_COLOR_RAMP, type)) {
-                val colors = complicationData.colorRamp
-                if (colors != null) {
-                    oos.writeBoolean(true)
-                    oos.writeInt(colors.size)
-                    for (color in colors) {
-                        oos.writeInt(color)
-                    }
-                } else {
-                    oos.writeBoolean(false)
-                }
+                oos.writeNullable(complicationData.colorRamp, oos::writeIntArray)
             }
             if (isFieldValidForType(FIELD_COLOR_RAMP_INTERPOLATED, type)) {
-                val isColorRampSmoothShaded = complicationData.isColorRampInterpolated
-                if (isColorRampSmoothShaded != null) {
-                    oos.writeBoolean(true)
-                    oos.writeBoolean(isColorRampSmoothShaded)
-                } else {
-                    oos.writeBoolean(false)
-                }
+                oos.writeNullable(complicationData.isColorRampInterpolated, oos::writeBoolean)
             }
             if (isFieldValidForType(FIELD_ELEMENT_WEIGHTS, type)) {
-                val weights = complicationData.elementWeights
-                if (weights != null) {
-                    oos.writeBoolean(true)
-                    oos.writeInt(weights.size)
-                    for (weight in weights) {
-                        oos.writeFloat(weight)
-                    }
-                } else {
-                    oos.writeBoolean(false)
-                }
+                oos.writeNullable(complicationData.elementWeights, oos::writeFloatArray)
             }
             if (isFieldValidForType(FIELD_ELEMENT_COLORS, type)) {
-                val colors = complicationData.elementColors
-                if (colors != null) {
-                    oos.writeBoolean(true)
-                    oos.writeInt(colors.size)
-                    for (color in colors) {
-                        oos.writeInt(color)
-                    }
-                } else {
-                    oos.writeBoolean(false)
-                }
+                oos.writeNullable(complicationData.elementColors, oos::writeIntArray)
             }
             if (isFieldValidForType(FIELD_ELEMENT_BACKGROUND_COLOR, type)) {
                 oos.writeInt(complicationData.elementBackgroundColor)
@@ -242,31 +220,13 @@
                 oos.writeInt(complicationData.listStyleHint)
             }
             if (isFieldValidForType(EXP_FIELD_PROTO_LAYOUT_INTERACTIVE, type)) {
-                val bytes = complicationData.interactiveLayout
-                if (bytes == null) {
-                    oos.writeInt(0)
-                } else {
-                    oos.writeInt(bytes.size)
-                    oos.write(bytes)
-                }
+                oos.writeByteArray(complicationData.interactiveLayout ?: byteArrayOf())
             }
             if (isFieldValidForType(EXP_FIELD_PROTO_LAYOUT_AMBIENT, type)) {
-                val bytes = complicationData.ambientLayout
-                if (bytes == null) {
-                    oos.writeInt(0)
-                } else {
-                    oos.writeInt(bytes.size)
-                    oos.write(bytes)
-                }
+                oos.writeByteArray(complicationData.ambientLayout ?: byteArrayOf())
             }
             if (isFieldValidForType(EXP_FIELD_PROTO_LAYOUT_RESOURCES, type)) {
-                val bytes = complicationData.layoutResources
-                if (bytes == null) {
-                    oos.writeInt(0)
-                } else {
-                    oos.writeInt(bytes.size)
-                    oos.write(bytes)
-                }
+                oos.writeByteArray(complicationData.layoutResources ?: byteArrayOf())
             }
             if (isFieldValidForType(FIELD_DATA_SOURCE, type)) {
                 val componentName = complicationData.dataSource
@@ -286,32 +246,18 @@
             val end = complicationData.fields.getLong(FIELD_TIMELINE_END_TIME, -1)
             oos.writeLong(end)
             oos.writeInt(complicationData.fields.getInt(FIELD_TIMELINE_ENTRY_TYPE))
-            val listEntries = complicationData.listEntries
-            val listEntriesLength = listEntries?.size ?: 0
-            oos.writeInt(listEntriesLength)
-            if (listEntries != null) {
-                for (data in listEntries) {
-                    SerializedForm(data).writeObject(oos)
-                }
+            oos.writeList(complicationData.listEntries ?: listOf()) {
+                SerializedForm(it).writeObject(oos)
             }
             if (isFieldValidForType(FIELD_PLACEHOLDER_FIELDS, type)) {
-                val placeholder = complicationData.placeholder
-                if (placeholder == null) {
-                    oos.writeBoolean(false)
-                } else {
-                    oos.writeBoolean(true)
-                    SerializedForm(placeholder).writeObject(oos)
+                oos.writeNullable(complicationData.placeholder) {
+                    SerializedForm(it).writeObject(oos)
                 }
             }
 
             // This has to be last, since it's recursive.
-            val timeline = complicationData.timelineEntries
-            val timelineLength = timeline?.size ?: 0
-            oos.writeInt(timelineLength)
-            if (timeline != null) {
-                for (data in timeline) {
-                    SerializedForm(data).writeObject(oos)
-                }
+            oos.writeList(complicationData.timelineEntries ?: listOf()) {
+                SerializedForm(it).writeObject(oos)
             }
         }
 
@@ -371,6 +317,11 @@
             if (isFieldValidForType(FIELD_VALUE, type)) {
                 fields.putFloat(FIELD_VALUE, ois.readFloat())
             }
+            if (isFieldValidForType(FIELD_DYNAMIC_VALUE, type)) {
+                ois.readNullable { ois.readByteArray() }?.let {
+                    fields.putByteArray(FIELD_DYNAMIC_VALUE, it)
+                }
+            }
             if (isFieldValidForType(FIELD_VALUE_TYPE, type)) {
                 fields.putInt(FIELD_VALUE_TYPE, ois.readInt())
             }
@@ -383,32 +334,25 @@
             if (isFieldValidForType(FIELD_TARGET_VALUE, type)) {
                 fields.putFloat(FIELD_TARGET_VALUE, ois.readFloat())
             }
-            if (isFieldValidForType(FIELD_COLOR_RAMP, type) && ois.readBoolean()) {
-                val numColors = ois.readInt()
-                val colors = IntArray(numColors)
-                for (i in 0 until numColors) {
-                    colors[i] = ois.readInt()
+            if (isFieldValidForType(FIELD_COLOR_RAMP, type)) {
+                ois.readNullable { ois.readIntArray() }?.let {
+                    fields.putIntArray(FIELD_COLOR_RAMP, it)
                 }
-                fields.putIntArray(FIELD_COLOR_RAMP, colors)
             }
-            if (isFieldValidForType(FIELD_COLOR_RAMP_INTERPOLATED, type) && ois.readBoolean()) {
-                fields.putBoolean(FIELD_COLOR_RAMP_INTERPOLATED, ois.readBoolean())
-            }
-            if (isFieldValidForType(FIELD_ELEMENT_WEIGHTS, type) && ois.readBoolean()) {
-                val numWeights = ois.readInt()
-                val weights = FloatArray(numWeights)
-                for (i in 0 until numWeights) {
-                    weights[i] = ois.readFloat()
+            if (isFieldValidForType(FIELD_COLOR_RAMP_INTERPOLATED, type)) {
+                ois.readNullable { ois.readBoolean() }?.let {
+                    fields.putBoolean(FIELD_COLOR_RAMP_INTERPOLATED, it)
                 }
-                fields.putFloatArray(FIELD_ELEMENT_WEIGHTS, weights)
             }
-            if (isFieldValidForType(FIELD_ELEMENT_COLORS, type) && ois.readBoolean()) {
-                val numColors = ois.readInt()
-                val colors = IntArray(numColors)
-                for (i in 0 until numColors) {
-                    colors[i] = ois.readInt()
+            if (isFieldValidForType(FIELD_ELEMENT_WEIGHTS, type)) {
+                ois.readNullable { ois.readFloatArray() }?.let {
+                    fields.putFloatArray(FIELD_ELEMENT_WEIGHTS, it)
                 }
-                fields.putIntArray(FIELD_ELEMENT_COLORS, colors)
+            }
+            if (isFieldValidForType(FIELD_ELEMENT_COLORS, type)) {
+                ois.readNullable { ois.readIntArray() }?.let {
+                    fields.putIntArray(FIELD_ELEMENT_COLORS, it)
+                }
             }
             if (isFieldValidForType(FIELD_ELEMENT_BACKGROUND_COLOR, type)) {
                 fields.putInt(FIELD_ELEMENT_BACKGROUND_COLOR, ois.readInt())
@@ -475,47 +419,29 @@
             if (timelineEntryType != 0) {
                 fields.putInt(FIELD_TIMELINE_ENTRY_TYPE, timelineEntryType)
             }
-            val listEntriesLength = ois.readInt()
-            if (listEntriesLength != 0) {
-                val parcels = arrayOfNulls<Parcelable>(listEntriesLength)
-                for (i in 0 until listEntriesLength) {
-                    val entry = SerializedForm()
-                    entry.readObject(ois)
-                    parcels[i] = entry.complicationData!!.fields
-                }
-                fields.putParcelableArray(EXP_FIELD_LIST_ENTRIES, parcels)
-            }
+            ois.readList { SerializedForm().apply { readObject(ois) } }
+                .map { it.complicationData!!.fields }
+                .takeIf { it.isNotEmpty() }
+                ?.let { fields.putParcelableArray(EXP_FIELD_LIST_ENTRIES, it.toTypedArray()) }
             if (isFieldValidForType(FIELD_PLACEHOLDER_FIELDS, type)) {
-                if (ois.readBoolean()) {
-                    val serializedPlaceholder = SerializedForm()
-                    serializedPlaceholder.readObject(ois)
-                    fields.putInt(
-                        FIELD_PLACEHOLDER_TYPE,
-                        serializedPlaceholder.complicationData!!.type
-                    )
-                    fields.putBundle(
-                        FIELD_PLACEHOLDER_FIELDS,
-                        serializedPlaceholder.complicationData!!.fields
-                    )
+                ois.readNullable { SerializedForm().apply { readObject(ois) } }?.let {
+                    fields.putInt(FIELD_PLACEHOLDER_TYPE, it.complicationData!!.type)
+                    fields.putBundle(FIELD_PLACEHOLDER_FIELDS, it.complicationData!!.fields)
                 }
             }
-            val timelineLength = ois.readInt()
-            if (timelineLength != 0) {
-                val parcels = arrayOfNulls<Parcelable>(timelineLength)
-                for (i in 0 until timelineLength) {
-                    val entry = SerializedForm()
-                    entry.readObject(ois)
-                    parcels[i] = entry.complicationData!!.fields
+            ois.readList { SerializedForm().apply { readObject(ois) } }
+                .map { it.complicationData!!.fields }
+                .takeIf { it.isNotEmpty() }
+                ?.let {
+                    fields.putParcelableArray(FIELD_TIMELINE_ENTRIES, it.toTypedArray())
                 }
-                fields.putParcelableArray(FIELD_TIMELINE_ENTRIES, parcels)
-            }
             complicationData = ComplicationData(type, fields)
         }
 
         fun readResolve(): Any = complicationData!!
 
         companion object {
-            private const val VERSION_NUMBER = 19
+            private const val VERSION_NUMBER = 20
             internal fun putIfNotNull(fields: Bundle, field: String, value: Parcelable?) {
                 if (value != null) {
                     fields.putParcelable(field, value)
@@ -650,7 +576,7 @@
         }
 
     /**
-     * Returns true if the ComplicationData contains a ranged max value. I.e. if [rangedValue] can
+     * Returns true if the ComplicationData contains a ranged value. I.e. if [rangedValue] can
      * succeed.
      */
     fun hasRangedValue(): Boolean = isFieldValidForType(FIELD_VALUE, type)
@@ -658,8 +584,8 @@
     /**
      * Returns the *value* field for this complication.
      *
-     * Valid only if the type of this complication data is [TYPE_RANGED_VALUE], otherwise returns
-     * zero.
+     * Valid only if the type of this complication data is [TYPE_RANGED_VALUE] and
+     * [TYPE_GOAL_PROGRESS], otherwise returns zero.
      */
     val rangedValue: Float
         get() {
@@ -668,6 +594,24 @@
         }
 
     /**
+     * Returns true if the ComplicationData contains a ranged dynamic value. I.e. if
+     * [rangedDynamicValue] can succeed.
+     */
+    fun hasRangedDynamicValue(): Boolean = isFieldValidForType(FIELD_DYNAMIC_VALUE, type)
+
+    /**
+     * Returns the *dynamicValue* field for this complication.
+     *
+     * Valid only if the type of this complication data is [TYPE_RANGED_VALUE] and
+     * [TYPE_GOAL_PROGRESS].
+     */
+    val rangedDynamicValue: DynamicFloat?
+        get() {
+            checkFieldValidForTypeWithoutThrowingException(FIELD_DYNAMIC_VALUE, type)
+            return fields.getByteArray(FIELD_DYNAMIC_VALUE)?.toDynamicFloat()
+        }
+
+    /**
      * Returns true if the ComplicationData contains a ranged max type. I.e. if [rangedValueType]
      * can succeed.
      */
@@ -1317,7 +1261,18 @@
          *
          * @throws IllegalStateException if this field is not valid for the complication type
          */
-        fun setRangedValue(value: Float) = apply { putFloatField(FIELD_VALUE, value) }
+        fun setRangedValue(value: Float?) = apply { putOrRemoveField(FIELD_VALUE, value) }
+
+        /**
+         * Sets the *dynamicValue* field. It is evaluated to a value with the same limitations as
+         * [setRangedValue].
+         *
+         * Returns this Builder to allow chaining.
+         *
+         * @throws IllegalStateException if this field is not valid for the complication type
+         */
+        fun setRangedDynamicValue(value: DynamicFloat?) =
+            apply { putOrRemoveField(FIELD_DYNAMIC_VALUE, value?.asByteArray()) }
 
         /**
          * Sets the *value type* field which provides meta data about the value. This is
@@ -1709,6 +1664,11 @@
                         " is provided."
                 }
             }
+            for (requiredOneOfFieldGroup in REQUIRED_ONE_OF_FIELDS[type]!!) {
+                check(requiredOneOfFieldGroup.count { fields.containsKey(it) } >= 1) {
+                    "One of $requiredOneOfFieldGroup must be provided."
+                }
+            }
             return ComplicationData(this)
         }
 
@@ -1737,8 +1697,10 @@
             when (obj) {
                 is Boolean -> fields.putBoolean(field, obj)
                 is Int -> fields.putInt(field, obj)
+                is Float -> fields.putFloat(field, obj)
                 is String -> fields.putString(field, obj)
                 is Parcelable -> fields.putParcelable(field, obj)
+                is ByteArray -> fields.putByteArray(field, obj)
                 is IntArray -> fields.putIntArray(field, obj)
                 is FloatArray -> fields.putFloatArray(field, obj)
                 else -> throw IllegalArgumentException("Unexpected object type: " + obj.javaClass)
@@ -1961,6 +1923,7 @@
         private const val FIELD_TIMELINE_ENTRIES = "TIMELINE"
         private const val FIELD_TIMELINE_ENTRY_TYPE = "TIMELINE_ENTRY_TYPE"
         private const val FIELD_VALUE = "VALUE"
+        private const val FIELD_DYNAMIC_VALUE = "DYNAMIC_VALUE"
         private const val FIELD_VALUE_TYPE = "VALUE_TYPE"
 
         // Experimental fields, these are subject to change without notice.
@@ -1999,7 +1962,7 @@
             TYPE_EMPTY to setOf(),
             TYPE_SHORT_TEXT to setOf(FIELD_SHORT_TEXT),
             TYPE_LONG_TEXT to setOf(FIELD_LONG_TEXT),
-            TYPE_RANGED_VALUE to setOf(FIELD_VALUE, FIELD_MIN_VALUE, FIELD_MAX_VALUE),
+            TYPE_RANGED_VALUE to setOf(FIELD_MIN_VALUE, FIELD_MAX_VALUE),
             TYPE_ICON to setOf(FIELD_ICON),
             TYPE_SMALL_IMAGE to setOf(FIELD_SMALL_IMAGE, FIELD_IMAGE_STYLE),
             TYPE_LARGE_IMAGE to setOf(FIELD_LARGE_IMAGE),
@@ -2008,18 +1971,39 @@
             EXP_TYPE_PROTO_LAYOUT to setOf(
                 EXP_FIELD_PROTO_LAYOUT_AMBIENT,
                 EXP_FIELD_PROTO_LAYOUT_INTERACTIVE,
-                EXP_FIELD_PROTO_LAYOUT_RESOURCES
+                EXP_FIELD_PROTO_LAYOUT_RESOURCES,
             ),
             EXP_TYPE_LIST to setOf(EXP_FIELD_LIST_ENTRIES),
-            TYPE_GOAL_PROGRESS to setOf(FIELD_VALUE, FIELD_TARGET_VALUE),
+            TYPE_GOAL_PROGRESS to setOf(FIELD_TARGET_VALUE),
             TYPE_WEIGHTED_ELEMENTS to setOf(
                 FIELD_ELEMENT_WEIGHTS,
                 FIELD_ELEMENT_COLORS,
-                FIELD_ELEMENT_BACKGROUND_COLOR
+                FIELD_ELEMENT_BACKGROUND_COLOR,
             ),
         )
 
-        // Used for validation. OPTIONAL_FIELDS[i] is an array containing all the fields which are
+        // Used for validation. REQUIRED_ONE_OF_FIELDS[i] is a list of field groups of which at
+        // least one field must be populated for @ComplicationType i.
+        // If a field is also in REQUIRED_FIELDS[i], it is not required if another field in the one
+        // of group is populated.
+        private val REQUIRED_ONE_OF_FIELDS: Map<Int, Set<Set<String>>> = mapOf(
+            TYPE_NOT_CONFIGURED to setOf(),
+            TYPE_EMPTY to setOf(),
+            TYPE_SHORT_TEXT to setOf(),
+            TYPE_LONG_TEXT to setOf(),
+            TYPE_RANGED_VALUE to setOf(setOf(FIELD_VALUE, FIELD_DYNAMIC_VALUE)),
+            TYPE_ICON to setOf(),
+            TYPE_SMALL_IMAGE to setOf(),
+            TYPE_LARGE_IMAGE to setOf(),
+            TYPE_NO_PERMISSION to setOf(),
+            TYPE_NO_DATA to setOf(),
+            EXP_TYPE_PROTO_LAYOUT to setOf(),
+            EXP_TYPE_LIST to setOf(),
+            TYPE_GOAL_PROGRESS to setOf(setOf(FIELD_VALUE, FIELD_DYNAMIC_VALUE)),
+            TYPE_WEIGHTED_ELEMENTS to setOf(),
+        )
+
+        // Used for validation. OPTIONAL_FIELDS[i] is a list containing all the fields which are
         // valid but not required for type i.
         private val OPTIONAL_FIELDS: Map<Int, Set<String>> = mapOf(
             TYPE_NOT_CONFIGURED to setOf(),
@@ -2035,7 +2019,7 @@
                 FIELD_CONTENT_DESCRIPTION,
                 FIELD_DATA_SOURCE,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
             TYPE_LONG_TEXT to setOf(
                 FIELD_LONG_TITLE,
@@ -2048,7 +2032,7 @@
                 FIELD_CONTENT_DESCRIPTION,
                 FIELD_DATA_SOURCE,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
             TYPE_RANGED_VALUE to setOf(
                 FIELD_SHORT_TEXT,
@@ -2065,7 +2049,7 @@
                 FIELD_COLOR_RAMP_INTERPOLATED,
                 FIELD_PERSISTENCE_POLICY,
                 FIELD_DISPLAY_POLICY,
-                FIELD_VALUE_TYPE
+                FIELD_VALUE_TYPE,
             ),
             TYPE_ICON to setOf(
                 FIELD_TAP_ACTION,
@@ -2073,7 +2057,7 @@
                 FIELD_CONTENT_DESCRIPTION,
                 FIELD_DATA_SOURCE,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
             TYPE_SMALL_IMAGE to setOf(
                 FIELD_TAP_ACTION,
@@ -2081,14 +2065,14 @@
                 FIELD_CONTENT_DESCRIPTION,
                 FIELD_DATA_SOURCE,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
             TYPE_LARGE_IMAGE to setOf(
                 FIELD_TAP_ACTION,
                 FIELD_CONTENT_DESCRIPTION,
                 FIELD_DATA_SOURCE,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
             TYPE_NO_PERMISSION to setOf(
                 FIELD_SHORT_TEXT,
@@ -2101,7 +2085,7 @@
                 FIELD_CONTENT_DESCRIPTION,
                 FIELD_DATA_SOURCE,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
             TYPE_NO_DATA to setOf(
                 FIELD_CONTENT_DESCRIPTION,
@@ -2121,17 +2105,18 @@
                 FIELD_SMALL_IMAGE_BURN_IN_PROTECTION,
                 FIELD_TAP_ACTION,
                 FIELD_VALUE,
+                FIELD_DYNAMIC_VALUE,
                 FIELD_VALUE_TYPE,
                 FIELD_DATA_SOURCE,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
             EXP_TYPE_PROTO_LAYOUT to setOf(
                 FIELD_TAP_ACTION,
                 FIELD_CONTENT_DESCRIPTION,
                 FIELD_DATA_SOURCE,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
             EXP_TYPE_LIST to setOf(
                 FIELD_TAP_ACTION,
@@ -2139,7 +2124,7 @@
                 FIELD_CONTENT_DESCRIPTION,
                 FIELD_DATA_SOURCE,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
             TYPE_GOAL_PROGRESS to setOf(
                 FIELD_SHORT_TEXT,
@@ -2155,7 +2140,7 @@
                 FIELD_COLOR_RAMP,
                 FIELD_COLOR_RAMP_INTERPOLATED,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
             TYPE_WEIGHTED_ELEMENTS to setOf(
                 FIELD_SHORT_TEXT,
@@ -2169,7 +2154,7 @@
                 FIELD_CONTENT_DESCRIPTION,
                 FIELD_DATA_SOURCE,
                 FIELD_PERSISTENCE_POLICY,
-                FIELD_DISPLAY_POLICY
+                FIELD_DISPLAY_POLICY,
             ),
         )
 
@@ -2181,35 +2166,28 @@
         }
 
         fun isFieldValidForType(field: String, @ComplicationType type: Int): Boolean {
-            val requiredFields = REQUIRED_FIELDS[type] ?: return false
-            for (requiredField in requiredFields) {
-                if (requiredField == field) {
-                    return true
-                }
-            }
-            for (optionalField in OPTIONAL_FIELDS[type]!!) {
-                if (optionalField == field) {
-                    return true
-                }
-            }
-            return false
+            return REQUIRED_FIELDS[type]!!.contains(field) ||
+                REQUIRED_ONE_OF_FIELDS[type]!!.any { it.contains(field) } ||
+                OPTIONAL_FIELDS[type]!!.contains(field)
         }
 
         private fun isTypeSupported(type: Int) = type in VALID_TYPES
 
-        /** The unparceling logic needs to remain backward compatible. */
+        /**
+         * The unparceling logic needs to remain backward compatible.
+         * Validates that a value of the given field type can be assigned
+         * to the given complication type.
+         */
         internal fun checkFieldValidForTypeWithoutThrowingException(
-            field: String,
-            @ComplicationType type: Int,
+            fieldType: String,
+            @ComplicationType complicationType: Int,
         ) {
-            if (!isTypeSupported(type)) {
-                Log.w(TAG, "Type $type can not be recognized")
+            if (!isTypeSupported(complicationType)) {
+                Log.w(TAG, "Type $complicationType can not be recognized")
                 return
             }
-            if (!isFieldValidForType(field, type)) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Field $field is not supported for type $type")
-                }
+            if (!isFieldValidForType(fieldType, complicationType)) {
+                Log.d(TAG, "Field $fieldType is not supported for type $complicationType")
             }
         }
 
@@ -2234,4 +2212,60 @@
             if (!shouldRedact() || unredacted == PLACEHOLDER_STRING) unredacted
             else "REDACTED"
     }
-}
\ No newline at end of file
+}
+
+/** Writes a [ByteArray] by writing the size, then the bytes. To be used with [readByteArray]. */
+internal fun ObjectOutputStream.writeByteArray(value: ByteArray) {
+    writeInt(value.size)
+    write(value)
+}
+
+/** Reads a [ByteArray] written with [writeByteArray]. */
+internal fun ObjectInputStream.readByteArray() = ByteArray(readInt()).also { readFully(it) }
+
+/** Writes an [IntArray] by writing the size, then the bytes. To be used with [readIntArray]. */
+internal fun ObjectOutputStream.writeIntArray(value: IntArray) {
+    writeInt(value.size)
+    value.forEach(this::writeInt)
+}
+
+/** Reads an [IntArray] written with [writeIntArray]. */
+internal fun ObjectInputStream.readIntArray() = IntArray(readInt()).also {
+    for (i in it.indices) it[i] = readInt()
+}
+
+/** Writes a [FloatArray] by writing the size, then the bytes. To be used with [readFloatArray]. */
+internal fun ObjectOutputStream.writeFloatArray(value: FloatArray) {
+    writeInt(value.size)
+    value.forEach(this::writeFloat)
+}
+
+/** Reads a [FloatArray] written with [writeFloatArray]. */
+internal fun ObjectInputStream.readFloatArray() = FloatArray(readInt()).also {
+    for (i in it.indices) it[i] = readFloat()
+}
+
+/** Writes a generic [List] by writing the size, then the objects. To be used with [readList]. */
+internal fun <T> ObjectOutputStream.writeList(value: List<T>, writer: (T) -> Unit) {
+    writeInt(value.size)
+    value.forEach(writer)
+}
+
+/** Reads a list written with [readList]. */
+internal fun <T> ObjectInputStream.readList(reader: () -> T) = List(readInt()) { reader() }
+
+/**
+ * Writes a nullable object by writing a boolean, then the object. To be used with [readNullable].
+ */
+internal fun <T> ObjectOutputStream.writeNullable(value: T?, writer: (T) -> Unit) {
+    if (value != null) {
+        writeBoolean(true)
+        writer(value)
+    } else {
+        writeBoolean(false)
+    }
+}
+
+/** Reads a nullable value written with [writeNullable]. */
+internal fun <T> ObjectInputStream.readNullable(reader: () -> T): T? =
+    if (readBoolean()) reader() else null
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
index 73e985a..d774957 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ComplicationExperimental::class)
+
 package androidx.wear.watchface.complications.data
 
 import android.app.PendingIntent
@@ -23,8 +25,8 @@
 import android.os.Build
 import android.util.Log
 import androidx.annotation.ColorInt
-import androidx.annotation.IntDef
 import androidx.annotation.FloatRange
+import androidx.annotation.IntDef
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import java.time.Instant
@@ -248,7 +250,7 @@
     cachedWireComplicationData,
     dataSource = null,
     persistencePolicy =
-        placeholder?.persistencePolicy ?: ComplicationPersistencePolicies.CACHING_ALLOWED,
+    placeholder?.persistencePolicy ?: ComplicationPersistencePolicies.CACHING_ALLOWED,
     displayPolicy = placeholder?.displayPolicy ?: ComplicationDisplayPolicies.ALWAYS_DISPLAY
 ) {
 
@@ -907,8 +909,8 @@
  * Type used for complications including a numerical value within a range, such as a percentage.
  * The value may be accompanied by an icon and/or short text and title.
  *
- * The [value], [min], and [max] fields are required for this type and the value within the
- * range is expected to always be displayed.
+ * The [min] and [max] fields are required for this type, as well as one of [value] or
+ * [dynamicValue]. The value within the range is expected to always be displayed.
  *
  * The icon, title, and text fields are optional and the watch face may choose which of these
  * fields to display, if any.
@@ -923,6 +925,9 @@
  * [PLACEHOLDER]. If it's equal to [PLACEHOLDER] the renderer must treat it as a placeholder rather
  * than rendering normally, its suggested to be drawn as a grey arc with a percentage value selected
  * by the renderer. The semantic meaning of value is described by [valueType].
+ * @property dynamicValue The [DynamicFloat] optionally set by the data source. If present the
+ * system will dynamically evaluate this and store the result in [value]. Watch faces can typically
+ * ignore this field.
  * @property min The minimum [Float] value for this complication.
  * @property max The maximum [Float] value for this complication.
  * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch
@@ -958,6 +963,10 @@
  */
 public class RangedValueComplicationData internal constructor(
     public val value: Float,
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @ComplicationExperimental
+    @get:ComplicationExperimental
+    public val dynamicValue: DynamicFloat?,
     public val min: Float,
     public val max: Float,
     public val monochromaticImage: MonochromaticImage?,
@@ -989,22 +998,55 @@
     /**
      * Builder for [RangedValueComplicationData].
      *
-     * You must at a minimum set the [value], [min], [max] and [contentDescription] fields and at
-     * least one of [monochromaticImage], [smallImage], [text] or [title].
-     *
-     * @param value The value of the ranged complication which should be in the range
-     * [[min]] .. [[max]]. The semantic meaning of value can be specified via [setValueType].
-     * @param min The minimum value. For [TYPE_PERCENTAGE] this must be 0f.
-     * @param max The maximum value. This must be less than [Float.MAX_VALUE]. For [TYPE_PERCENTAGE]
-     * this must be 100f.
-     * @param contentDescription Localized description for use by screen readers
+     * You must at a minimum set the [min], [max] and [contentDescription] fields, at least one of
+     * [value] or [dynamicValue], and at least one of [monochromaticImage], [smallImage], [text]
+     * or [title].
      */
-    public class Builder(
+    public class Builder
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public constructor(
         private val value: Float,
+        private val dynamicValue: DynamicFloat?,
         private val min: Float,
         private val max: Float,
         private var contentDescription: ComplicationText
     ) : BaseBuilder<Builder, RangedValueComplicationData>() {
+        /**
+         * Creates a [Builder] for a [RangedValueComplicationData] with a [Float] value.
+         *
+         * @param value The value of the ranged complication which should be in the range [[min]] ..
+         * [[max]]. The semantic meaning of value can be specified via [setValueType].
+         * @param min The minimum value. For [TYPE_PERCENTAGE] this must be 0f.
+         * @param max The maximum value. This must be less than [Float.MAX_VALUE]. For
+         * [TYPE_PERCENTAGE] this must be 0f.
+         * @param contentDescription Localized description for use by screen readers
+         */
+        public constructor(
+            value: Float,
+            min: Float,
+            max: Float,
+            contentDescription: ComplicationText
+        ) : this(value, dynamicValue = null, min, max, contentDescription)
+
+        /**
+         * Creates a [Builder] for a [RangedValueComplicationData] with a [DynamicFloat] value.
+         *
+         * @param dynamicValue The [DynamicFloat] of the ranged complication which will be evaluated
+         * into a value dynamically, and should be in the range [[min]] .. [[max]]. The semantic
+         * meaning of value can be specified via [setValueType].
+         * @param min The minimum value. For [TYPE_PERCENTAGE] this must be 0f.
+         * @param max The maximum value. This must be less than [Float.MAX_VALUE]. For
+         * [TYPE_PERCENTAGE] this must be 0f.
+         * @param contentDescription Localized description for use by screen readers
+         */
+        @ComplicationExperimental
+        public constructor(
+            dynamicValue: DynamicFloat,
+            min: Float,
+            max: Float,
+            contentDescription: ComplicationText
+        ) : this(value = min /* sensible default */, dynamicValue, min, max, contentDescription)
+
         private var tapAction: PendingIntent? = null
         private var validTimeRange: TimeRange? = null
         private var monochromaticImage: MonochromaticImage? = null
@@ -1012,6 +1054,7 @@
         private var title: ComplicationText? = null
         private var text: ComplicationText? = null
         private var colorRamp: ColorRamp? = null
+
         @RangedValueType
         private var valueType: Int = TYPE_UNDEFINED
 
@@ -1082,6 +1125,7 @@
             }
             return RangedValueComplicationData(
                 value,
+                dynamicValue,
                 min,
                 max,
                 monochromaticImage,
@@ -1114,6 +1158,7 @@
 
     override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
         builder.setRangedValue(value)
+        builder.setRangedDynamicValue(dynamicValue)
         builder.setRangedMinValue(min)
         builder.setRangedMaxValue(max)
         monochromaticImage?.addToWireComplicationData(builder)
@@ -1143,6 +1188,7 @@
         other as RangedValueComplicationData
 
         if (value != other.value) return false
+        if (dynamicValue != other.dynamicValue) return false
         if (valueType != other.valueType) return false
         if (min != other.min) return false
         if (max != other.max) return false
@@ -1164,6 +1210,7 @@
 
     override fun hashCode(): Int {
         var result = value.hashCode()
+        result = 31 * result + (dynamicValue?.hashCode() ?: 0)
         result = 31 * result + valueType
         result = 31 * result + min.hashCode()
         result = 31 * result + max.hashCode()
@@ -1188,7 +1235,13 @@
         } else {
             value.toString()
         }
-        return "RangedValueComplicationData(value=$valueString, valueType=$valueType, min=$min, " +
+        val dynamicValueString = if (WireComplicationData.shouldRedact()) {
+            "REDACTED"
+        } else {
+            dynamicValue.toString()
+        }
+        return "RangedValueComplicationData(value=$valueString, " +
+            "dynamicValue=$dynamicValueString, valueType=$valueType, min=$min, " +
             "max=$max, monochromaticImage=$monochromaticImage, smallImage=$smallImage, " +
             "title=$title, text=$text, contentDescription=$contentDescription), " +
             "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
@@ -1255,8 +1308,8 @@
  * color to indicate progress past the goal). The value may be accompanied by an icon and/or short
  * text and title.
  *
- * The [value], and [targetValue] fields are required for this type and the progress is expected to
- * always be displayed.
+ * The [targetValue] field is required for this type, as well as one of [value] or
+ * [dynamicValue]. The progress is expected to always be displayed.
  *
  * The icon, title, and text fields are optional and the watch face may choose which of these
  * fields to display, if any.
@@ -1269,12 +1322,16 @@
  *
  * If you want to represent a score for something that's not based on the user (e.g. air quality
  * index) then you should instead use a [RangedValueComplicationData] and pass
- * [RangedValueComplicationData.TYPE_RATING] into [RangedValueComplicationData.Builder.setValueType].
+ * [RangedValueComplicationData.TYPE_RATING] into
+ * [RangedValueComplicationData.Builder.setValueType].
  *
  * @property value The [Float] value of this complication which is >= 0f, this value may be larger
  * than [targetValue]. If it's equal to [PLACEHOLDER] the renderer must treat it as a placeholder
  * rather than rendering normally, its suggested to be drawn as a grey arc with a percentage value
  * selected by the renderer.
+ * @property dynamicValue The [DynamicFloat] optionally set by the data source. If present the
+ * system will dynamically evaluate this and store the result in [value]. Watch faces can typically
+ * ignore this field.
  * @property targetValue The target [Float] value for this complication.
  * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch
  * face. If the monochromaticImage is equal to [MonochromaticImage.PLACEHOLDER] the renderer must
@@ -1308,6 +1365,10 @@
 public class GoalProgressComplicationData
 internal constructor(
     public val value: Float,
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @ComplicationExperimental
+    @get:ComplicationExperimental
+    public val dynamicValue: DynamicFloat?,
     public val targetValue: Float,
     public val monochromaticImage: MonochromaticImage?,
     public val smallImage: SmallImage?,
@@ -1333,19 +1394,52 @@
     /**
      * Builder for [GoalProgressComplicationData].
      *
-     * You must at a minimum set the [value], [targetValue] and [contentDescription] fields and at
-     * least one of [monochromaticImage], [smallImage], [text] or [title].
-     *
-     * @param value The value of the ranged complication which should be >= 0.
-     * @param targetValue The target value. This must be less than [Float.MAX_VALUE].
-     * @param contentDescription Localized description for use by screen readers
+     * You must at a minimum set the [targetValue] and [contentDescription] fields, one of [value]
+     * or [dynamicValue], and at least one of [monochromaticImage], [smallImage], [text] or
+     * [title].
      */
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-    public class Builder(
+    public class Builder
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public constructor(
         private val value: Float,
+        private val dynamicValue: DynamicFloat?,
         private val targetValue: Float,
         private var contentDescription: ComplicationText
     ) : BaseBuilder<Builder, GoalProgressComplicationData>() {
+        /**
+         * Creates a [Builder] for a [GoalProgressComplicationData] with a [Float] value.
+         *
+         * @param value The value of the goal complication which should be >= 0.
+         * @param targetValue The target value. This must be less than [Float.MAX_VALUE].
+         * @param contentDescription Localized description for use by screen readers
+         */
+        public constructor(
+            value: Float,
+            targetValue: Float,
+            contentDescription: ComplicationText
+        ) : this(value, dynamicValue = null, targetValue, contentDescription)
+
+        /**
+         * Creates a [Builder] for a [GoalProgressComplicationData] with a [DynamicFloat] value.
+         *
+         * @param dynamicValue The [DynamicFloat] of the goal complication which will be evaluated
+         * into a value dynamically, and should be >= 0.
+         * @param targetValue The target value. This must be less than [Float.MAX_VALUE].
+         * @param contentDescription Localized description for use by screen readers
+         */
+        @ComplicationExperimental
+        public constructor(
+            dynamicValue: DynamicFloat,
+            targetValue: Float,
+            contentDescription: ComplicationText
+        ) : this(
+            value = 0f /* sensible default */,
+            dynamicValue,
+            targetValue,
+            contentDescription
+        )
+
         private var tapAction: PendingIntent? = null
         private var validTimeRange: TimeRange? = null
         private var monochromaticImage: MonochromaticImage? = null
@@ -1408,6 +1502,7 @@
             }
             return GoalProgressComplicationData(
                 value,
+                dynamicValue,
                 targetValue,
                 monochromaticImage,
                 smallImage,
@@ -1438,6 +1533,7 @@
 
     override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
         builder.setRangedValue(value)
+        builder.setRangedDynamicValue(dynamicValue)
         builder.setTargetValue(targetValue)
         monochromaticImage?.addToWireComplicationData(builder)
         smallImage?.addToWireComplicationData(builder)
@@ -1465,6 +1561,7 @@
         other as GoalProgressComplicationData
 
         if (value != other.value) return false
+        if (dynamicValue != other.dynamicValue) return false
         if (targetValue != other.targetValue) return false
         if (monochromaticImage != other.monochromaticImage) return false
         if (smallImage != other.smallImage) return false
@@ -1484,6 +1581,7 @@
 
     override fun hashCode(): Int {
         var result = value.hashCode()
+        result = 31 * result + (dynamicValue?.hashCode() ?: 0)
         result = 31 * result + targetValue.hashCode()
         result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
         result = 31 * result + (smallImage?.hashCode() ?: 0)
@@ -1506,7 +1604,13 @@
         } else {
             value.toString()
         }
-        return "GoalProgressComplicationData(value=$valueString, targetValue=$targetValue, " +
+        val dynamicValueString = if (WireComplicationData.shouldRedact()) {
+            "REDACTED"
+        } else {
+            dynamicValue.toString()
+        }
+        return "GoalProgressComplicationData(value=$valueString, " +
+            "dynamicValue=$dynamicValueString, targetValue=$targetValue, " +
             "monochromaticImage=$monochromaticImage, smallImage=$smallImage, title=$title, " +
             "text=$text, contentDescription=$contentDescription), " +
             "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
@@ -1684,7 +1788,8 @@
         elements: List<Element>,
         private var contentDescription: ComplicationText
     ) : BaseBuilder<Builder, WeightedElementsComplicationData>() {
-        @ColorInt private var elementBackgroundColor: Int = Color.TRANSPARENT
+        @ColorInt
+        private var elementBackgroundColor: Int = Color.TRANSPARENT
         private var tapAction: PendingIntent? = null
         private var validTimeRange: TimeRange? = null
         private var monochromaticImage: MonochromaticImage? = null
@@ -2564,6 +2669,7 @@
             RangedValueComplicationData.TYPE.toWireComplicationType() ->
                 RangedValueComplicationData.Builder(
                     value = rangedValue,
+                    dynamicValue = rangedDynamicValue,
                     min = rangedMinValue,
                     max = rangedMaxValue,
                     contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
@@ -2622,6 +2728,7 @@
             GoalProgressComplicationData.TYPE.toWireComplicationType() ->
                 GoalProgressComplicationData.Builder(
                     value = rangedValue,
+                    dynamicValue = rangedDynamicValue,
                     targetValue = targetValue,
                     contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
                 ).apply {
@@ -2738,7 +2845,9 @@
 
             RangedValueComplicationData.TYPE.toWireComplicationType() ->
                 RangedValueComplicationData.Builder(
-                    value = rangedValue, min = rangedMinValue,
+                    value = rangedValue,
+                    dynamicValue = rangedDynamicValue,
+                    min = rangedMinValue,
                     max = rangedMaxValue,
                     contentDescription = contentDescription?.toApiComplicationText()
                         ?: ComplicationText.EMPTY
@@ -2813,6 +2922,7 @@
             GoalProgressComplicationData.TYPE.toWireComplicationType() ->
                 GoalProgressComplicationData.Builder(
                     value = rangedValue,
+                    dynamicValue = rangedDynamicValue,
                     targetValue = targetValue,
                     contentDescription = contentDescription?.toApiComplicationText()
                         ?: ComplicationText.EMPTY
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/DynamicFloat.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/DynamicFloat.kt
new file mode 100644
index 0000000..0374f72
--- /dev/null
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/DynamicFloat.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.watchface.complications.data
+
+import androidx.annotation.RestrictTo
+
+/** Placeholder for DynamicFloat implementation by tiles. */
+// TODO(b/257413268): Replace this with the real implementation.
+@ComplicationExperimental
+abstract class DynamicFloat {
+    abstract fun asByteArray(): ByteArray
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        // Not checking for exact same class because it's not implemented yet.
+        if (other !is DynamicFloat) return false
+        return asByteArray().contentEquals(other.asByteArray())
+    }
+
+    override fun hashCode() = asByteArray().contentHashCode()
+
+    override fun toString() = "DynamicFloatPlaceholder${asByteArray().contentToString()}"
+}
+
+/** Placeholder parser for [DynamicFloat] from [ByteArray]. */
+@ComplicationExperimental
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun ByteArray.toDynamicFloat() = object : DynamicFloat() {
+    override fun asByteArray() = this@toDynamicFloat
+}
\ No newline at end of file
diff --git a/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt b/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt
index d264e3f..4494346 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ComplicationExperimental::class)
+
 package android.support.wearable.complications
 
 import android.app.PendingIntent
@@ -23,7 +25,9 @@
 import android.support.wearable.complications.ComplicationText.TimeDifferenceBuilder
 import android.support.wearable.complications.ComplicationText.TimeFormatBuilder
 import androidx.test.core.app.ApplicationProvider
+import androidx.wear.watchface.complications.data.ComplicationExperimental
 import androidx.wear.watchface.complications.data.SharedRobolectricTestRunner
+import androidx.wear.watchface.complications.data.toDynamicFloat
 import com.google.common.truth.Truth
 import org.junit.Assert
 import org.junit.Assert.assertThrows
@@ -79,7 +83,7 @@
     }
 
     @Test
-    public fun testRangedValueFields() {
+    public fun testRangedValueFieldsWithFixedValue() {
         // GIVEN complication data of the RANGED_VALUE type created by the Builder...
         val data =
             ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
@@ -93,6 +97,30 @@
         // WHEN the relevant getters are called on the resulting data
         // THEN the correct values are returned.
         Assert.assertEquals(data.rangedValue, 57f, 0f)
+        Assert.assertNull(data.rangedDynamicValue)
+        Assert.assertEquals(data.rangedMinValue, 5f, 0f)
+        Assert.assertEquals(data.rangedMaxValue, 150f, 0f)
+        Truth.assertThat(data.shortTitle!!.getTextAt(mResources, 0))
+            .isEqualTo("title")
+        Truth.assertThat(data.shortText!!.getTextAt(mResources, 0))
+            .isEqualTo("text")
+    }
+
+    @Test
+    public fun testRangedValueFieldsWithDynamicValue() {
+        // GIVEN complication data of the RANGED_VALUE type created by the Builder...
+        val data =
+            ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
+                .setRangedDynamicValue(byteArrayOf(42, 107).toDynamicFloat())
+                .setRangedMinValue(5f)
+                .setRangedMaxValue(150f)
+                .setShortTitle(ComplicationText.plainText("title"))
+                .setShortText(ComplicationText.plainText("text"))
+                .build()
+
+        // WHEN the relevant getters are called on the resulting data
+        // THEN the correct values are returned.
+        Truth.assertThat(data.rangedDynamicValue!!.asByteArray()).isEqualTo(byteArrayOf(42, 107))
         Assert.assertEquals(data.rangedMinValue, 5f, 0f)
         Assert.assertEquals(data.rangedMaxValue, 150f, 0f)
         Truth.assertThat(data.shortTitle!!.getTextAt(mResources, 0))
@@ -128,7 +156,7 @@
     }
 
     @Test
-    public fun testRangedValueMustContainValue() {
+    public fun testRangedValueMustContainFixedOrDynamicValue() {
         // GIVEN a complication builder of the RANGED_VALUE type, with the value field not
         // populated...
         val builder =
@@ -1106,7 +1134,7 @@
                         .setLongText(
                             ComplicationText.plainText(ComplicationData.PLACEHOLDER_STRING)
                         )
-                            .build()
+                        .build()
                 )
                 .build()
         timelineEntry.timelineStartEpochSecond = 100
diff --git a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
index 7251d4b..44c76f8 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ComplicationExperimental::class)
+
 package androidx.wear.watchface.complications.data
 
 import android.annotation.SuppressLint
@@ -399,7 +401,7 @@
     }
 
     @Test
-    public fun rangedValueComplicationData() {
+    public fun rangedValueComplicationData_withFixedValue() {
         val data = RangedValueComplicationData.Builder(
             value = 95f, min = 0f, max = 100f,
             contentDescription = "content description".complicationText
@@ -426,6 +428,7 @@
         assertThat(deserialized.max).isEqualTo(100f)
         assertThat(deserialized.min).isEqualTo(0f)
         assertThat(deserialized.value).isEqualTo(95f)
+        assertThat(deserialized.dynamicValue).isNull()
         assertThat(deserialized.contentDescription!!.getTextAt(resources, Instant.EPOCH))
             .isEqualTo("content description")
         assertThat(deserialized.title!!.getTextAt(resources, Instant.EPOCH))
@@ -452,7 +455,95 @@
         assertThat(data.hashCode()).isEqualTo(data2.hashCode())
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
-            "RangedValueComplicationData(value=95.0, valueType=0, min=0.0, max=100.0, " +
+            "RangedValueComplicationData(value=95.0, dynamicValue=null, " +
+                "valueType=0, min=0.0, max=100.0, " +
+                "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
+                "mSurroundingText=battery, mTimeDependentText=null}, text=null, " +
+                "contentDescription=ComplicationText{mSurroundingText=content description, " +
+                "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
+                "tapAction=null, validTimeRange=TimeRange(" +
+                "startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
+                "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), dataSource=" +
+                "ComponentInfo{com.pkg_a/com.a}, colorRamp=null, persistencePolicy=0, " +
+                "displayPolicy=0)"
+        )
+    }
+
+    @Test
+    public fun rangedValueComplicationData_withDynamicValue() {
+        val data = RangedValueComplicationData.Builder(
+            dynamicValue = byteArrayOf(42, 107).toDynamicFloat(),
+            min = 5f,
+            max = 100f,
+            contentDescription = "content description".complicationText
+        )
+            .setTitle("battery".complicationText)
+            .setDataSource(dataSourceA)
+            .build()
+        ParcelableSubject.assertThat(data.asWireComplicationData())
+            .hasSameSerializationAs(
+                WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
+                    .setRangedDynamicValue(byteArrayOf(42, 107).toDynamicFloat())
+                    .setRangedValue(5f) // min as a sensible default
+                    .setRangedValueType(RangedValueComplicationData.TYPE_UNDEFINED)
+                    .setRangedMinValue(5f)
+                    .setRangedMaxValue(100f)
+                    .setShortTitle(WireComplicationText.plainText("battery"))
+                    .setContentDescription(WireComplicationText.plainText("content description"))
+                    .setDataSource(dataSourceA)
+                    .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
+                    .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
+                    .build()
+            )
+        testRoundTripConversions(data)
+        val deserialized = serializeAndDeserialize(data) as RangedValueComplicationData
+        assertThat(deserialized.max).isEqualTo(100f)
+        assertThat(deserialized.min).isEqualTo(5f)
+        assertThat(deserialized.dynamicValue!!.asByteArray()).isEqualTo(byteArrayOf(42, 107))
+        assertThat(deserialized.value).isEqualTo(5f) // min as a sensible default
+        assertThat(deserialized.contentDescription!!.getTextAt(resources, Instant.EPOCH))
+            .isEqualTo("content description")
+        assertThat(deserialized.title!!.getTextAt(resources, Instant.EPOCH))
+            .isEqualTo("battery")
+
+        val sameData = RangedValueComplicationData.Builder(
+            dynamicValue = byteArrayOf(42, 107).toDynamicFloat(),
+            min = 5f,
+            max = 100f,
+            contentDescription = "content description".complicationText
+        )
+            .setTitle("battery".complicationText)
+            .setDataSource(dataSourceA)
+            .build()
+        val diffDataFixedValue = RangedValueComplicationData.Builder(
+            value = 5f, // Even though it's the sensible default
+            min = 5f,
+            max = 100f,
+            contentDescription = "content description".complicationText
+        )
+            .setTitle("battery".complicationText)
+            .setDataSource(dataSourceA)
+            .build()
+        val diffDataDynamicValue = RangedValueComplicationData.Builder(
+            dynamicValue = byteArrayOf(43, 108).toDynamicFloat(),
+            min = 5f,
+            max = 100f,
+            contentDescription = "content description".complicationText
+        )
+            .setTitle("battery".complicationText)
+            .setDataSource(dataSourceA)
+            .build()
+
+        assertThat(data).isEqualTo(sameData)
+        assertThat(data).isNotEqualTo(diffDataFixedValue)
+        assertThat(data).isNotEqualTo(diffDataDynamicValue)
+        assertThat(data.hashCode()).isEqualTo(sameData.hashCode())
+        assertThat(data.hashCode()).isNotEqualTo(diffDataFixedValue.hashCode())
+        assertThat(data.hashCode()).isNotEqualTo(diffDataDynamicValue.hashCode())
+        assertThat(data.toString()).isEqualTo(
+            "RangedValueComplicationData(value=5.0, " +
+                "dynamicValue=DynamicFloatPlaceholder[42, 107], " +
+                "valueType=0, min=5.0, max=100.0, " +
                 "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
                 "mSurroundingText=battery, mTimeDependentText=null}, text=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
@@ -535,7 +626,8 @@
         assertThat(data.hashCode()).isEqualTo(data2.hashCode())
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
-            "RangedValueComplicationData(value=95.0, valueType=1, min=0.0, max=100.0, " +
+            "RangedValueComplicationData(value=95.0, dynamicValue=null, " +
+                "valueType=1, min=0.0, max=100.0, " +
                 "monochromaticImage=MonochromaticImage(image=Icon(typ=URI uri=someuri), " +
                 "ambientImage=null), smallImage=SmallImage(image=Icon(typ=URI uri=someuri2), " +
                 "type=PHOTO, ambientImage=null), title=ComplicationText{mSurroundingText=battery," +
@@ -550,7 +642,144 @@
     }
 
     @Test
-    public fun goalProgressComplicationData_with_ColorRamp() {
+    public fun goalProgressComplicationData_withFixedValue() {
+        val data = GoalProgressComplicationData.Builder(
+            value = 1200f, targetValue = 10000f,
+            contentDescription = "content description".complicationText
+        )
+            .setTitle("steps".complicationText)
+            .setDataSource(dataSourceA)
+            .build()
+        ParcelableSubject.assertThat(data.asWireComplicationData())
+            .hasSameSerializationAs(
+                WireComplicationDataBuilder(WireComplicationData.TYPE_GOAL_PROGRESS)
+                    .setRangedValue(1200f)
+                    .setTargetValue(10000f)
+                    .setShortTitle(WireComplicationText.plainText("steps"))
+                    .setContentDescription(WireComplicationText.plainText("content description"))
+                    .setDataSource(dataSourceA)
+                    .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
+                    .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
+                    .build()
+            )
+        testRoundTripConversions(data)
+        val deserialized = serializeAndDeserialize(data) as GoalProgressComplicationData
+        assertThat(deserialized.value).isEqualTo(1200f)
+        assertThat(deserialized.dynamicValue).isNull()
+        assertThat(deserialized.targetValue).isEqualTo(10000f)
+        assertThat(deserialized.contentDescription!!.getTextAt(resources, Instant.EPOCH))
+            .isEqualTo("content description")
+        assertThat(deserialized.title!!.getTextAt(resources, Instant.EPOCH))
+            .isEqualTo("steps")
+
+        val sameData = GoalProgressComplicationData.Builder(
+            value = 1200f, targetValue = 10000f,
+            contentDescription = "content description".complicationText
+        )
+            .setTitle("steps".complicationText)
+            .setDataSource(dataSourceA)
+            .build()
+
+        val diffData = GoalProgressComplicationData.Builder(
+            value = 1201f, targetValue = 10000f,
+            contentDescription = "content description".complicationText
+        )
+            .setTitle("steps".complicationText)
+            .setDataSource(dataSourceB)
+            .build()
+
+        assertThat(data).isEqualTo(sameData)
+        assertThat(data).isNotEqualTo(diffData)
+        assertThat(data.hashCode()).isEqualTo(sameData.hashCode())
+        assertThat(data.hashCode()).isNotEqualTo(diffData.hashCode())
+        assertThat(data.toString()).isEqualTo(
+            "GoalProgressComplicationData(value=1200.0, dynamicValue=null, " +
+                "targetValue=10000.0, monochromaticImage=null, smallImage=null, " +
+                "title=ComplicationText{mSurroundingText=steps, mTimeDependentText=null}, " +
+                "text=null, " +
+                "contentDescription=ComplicationText{mSurroundingText=content description, " +
+                "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
+                "tapAction=null, validTimeRange=TimeRange(startDateTimeMillis=" +
+                "-1000000000-01-01T00:00:00Z, endDateTimeMillis=" +
+                "+1000000000-12-31T23:59:59.999999999Z), dataSource=" +
+                "ComponentInfo{com.pkg_a/com.a}, colorRamp=null, " +
+                "persistencePolicy=0, displayPolicy=0)"
+        )
+    }
+
+    @Test
+    public fun goalProgressComplicationData_withDynamicValue() {
+        val data = GoalProgressComplicationData.Builder(
+            dynamicValue = byteArrayOf(42, 107).toDynamicFloat(),
+            targetValue = 10000f,
+            contentDescription = "content description".complicationText
+        )
+            .setTitle("steps".complicationText)
+            .setDataSource(dataSourceA)
+            .build()
+        ParcelableSubject.assertThat(data.asWireComplicationData())
+            .hasSameSerializationAs(
+                WireComplicationDataBuilder(WireComplicationData.TYPE_GOAL_PROGRESS)
+                    .setRangedDynamicValue(byteArrayOf(42, 107).toDynamicFloat())
+                    .setRangedValue(0f) // sensible default
+                    .setTargetValue(10000f)
+                    .setShortTitle(WireComplicationText.plainText("steps"))
+                    .setContentDescription(WireComplicationText.plainText("content description"))
+                    .setDataSource(dataSourceA)
+                    .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
+                    .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
+                    .build()
+            )
+        testRoundTripConversions(data)
+        val deserialized = serializeAndDeserialize(data) as GoalProgressComplicationData
+        assertThat(deserialized.dynamicValue!!.asByteArray()).isEqualTo(byteArrayOf(42, 107))
+        assertThat(deserialized.value).isEqualTo(0f) // sensible default
+        assertThat(deserialized.targetValue).isEqualTo(10000f)
+        assertThat(deserialized.contentDescription!!.getTextAt(resources, Instant.EPOCH))
+            .isEqualTo("content description")
+        assertThat(deserialized.title!!.getTextAt(resources, Instant.EPOCH))
+            .isEqualTo("steps")
+
+        val sameData = GoalProgressComplicationData.Builder(
+            dynamicValue = byteArrayOf(42, 107).toDynamicFloat(),
+            targetValue = 10000f,
+            contentDescription = "content description".complicationText
+        )
+            .setTitle("steps".complicationText)
+            .setDataSource(dataSourceA)
+            .build()
+
+        val diffData = GoalProgressComplicationData.Builder(
+            dynamicValue = byteArrayOf(43, 108).toDynamicFloat(),
+            targetValue = 10000f,
+            contentDescription = "content description".complicationText
+        )
+            .setTitle("steps".complicationText)
+            .setDataSource(dataSourceB)
+            .build()
+
+        assertThat(data).isEqualTo(sameData)
+        assertThat(data).isNotEqualTo(diffData)
+        assertThat(data.hashCode()).isEqualTo(sameData.hashCode())
+        assertThat(data.hashCode()).isNotEqualTo(diffData.hashCode())
+        assertThat(data.toString()).isEqualTo(
+            "GoalProgressComplicationData(value=0.0, " +
+                "dynamicValue=DynamicFloatPlaceholder[42, 107], " +
+                "targetValue=10000.0, monochromaticImage=null, smallImage=null, " +
+                "title=ComplicationText{mSurroundingText=steps, mTimeDependentText=null}, " +
+                "text=null, " +
+                "contentDescription=ComplicationText{mSurroundingText=content description, " +
+                "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
+                "tapAction=null, validTimeRange=TimeRange(startDateTimeMillis=" +
+                "-1000000000-01-01T00:00:00Z, endDateTimeMillis=" +
+                "+1000000000-12-31T23:59:59.999999999Z), dataSource=" +
+                "ComponentInfo{com.pkg_a/com.a}, colorRamp=null, " +
+                "persistencePolicy=0, displayPolicy=0)"
+        )
+    }
+
+    @Test
+    public fun goalProgressComplicationData_withColorRamp() {
         val data = GoalProgressComplicationData.Builder(
             value = 1200f, targetValue = 10000f,
             contentDescription = "content description".complicationText
@@ -605,9 +834,10 @@
         assertThat(data.hashCode()).isEqualTo(data2.hashCode())
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
-            "GoalProgressComplicationData(value=1200.0, targetValue=10000.0, " +
-                "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
-                "mSurroundingText=steps, mTimeDependentText=null}, text=null, " +
+            "GoalProgressComplicationData(value=1200.0, dynamicValue=null, " +
+                "targetValue=10000.0, monochromaticImage=null, smallImage=null, " +
+                "title=ComplicationText{mSurroundingText=steps, mTimeDependentText=null}, " +
+                "text=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
                 "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
                 "tapAction=null, validTimeRange=TimeRange(startDateTimeMillis=" +
@@ -620,7 +850,7 @@
 
     @RequiresApi(Build.VERSION_CODES.P)
     @Test
-    public fun goalProgressComplicationData_with_ColorRamp_and_Images() {
+    public fun goalProgressComplicationData_withColorRampAndImages() {
         val data = GoalProgressComplicationData.Builder(
             value = 1200f, targetValue = 10000f,
             contentDescription = "content description".complicationText
@@ -687,7 +917,8 @@
         assertThat(data.hashCode()).isEqualTo(data2.hashCode())
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
-            "GoalProgressComplicationData(value=1200.0, targetValue=10000.0, " +
+            "GoalProgressComplicationData(value=1200.0, dynamicValue=null, " +
+                "targetValue=10000.0, " +
                 "monochromaticImage=MonochromaticImage(image=Icon(typ=URI uri=someuri), " +
                 "ambientImage=null), smallImage=SmallImage(image=Icon(typ=URI uri=someuri2), " +
                 "type=PHOTO, ambientImage=null), title=ComplicationText{mSurroundingText=steps, " +
@@ -703,7 +934,7 @@
     }
 
     @Test
-    public fun rangedValueComplicationData_with_ColorRamp() {
+    public fun rangedValueComplicationData_withColorRamp() {
         val data = RangedValueComplicationData.Builder(
             value = 95f, min = 0f, max = 100f,
             contentDescription = "content description".complicationText
@@ -761,7 +992,8 @@
         assertThat(data.hashCode()).isEqualTo(data2.hashCode())
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
-            "RangedValueComplicationData(value=95.0, valueType=0, min=0.0, max=100.0, " +
+            "RangedValueComplicationData(value=95.0, dynamicValue=null, " +
+                "valueType=0, min=0.0, max=100.0, " +
                 "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
                 "mSurroundingText=battery, mTimeDependentText=null}, text=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
@@ -1093,6 +1325,7 @@
 
         assertThat(deserialized.smallImage.image.type).isEqualTo(Icon.TYPE_BITMAP)
         val getBitmap = deserialized.smallImage.image.javaClass.getDeclaredMethod("getBitmap")
+
         @SuppressLint("BanUncheckedReflection")
         val bitmap = getBitmap.invoke(deserialized.smallImage.image) as Bitmap
 
@@ -1455,8 +1688,8 @@
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
             "NoDataComplicationData(placeholder=RangedValueComplicationData(" +
-                "value=3.4028235E38, valueType=0, min=0.0, max=100.0, monochromaticImage=null, " +
-                "smallImage=null, title=null, text=ComplicationText{" +
+                "value=3.4028235E38, dynamicValue=null, valueType=0, min=0.0, max=100.0, " +
+                "monochromaticImage=null, smallImage=null, title=null, text=ComplicationText{" +
                 "mSurroundingText=__placeholder__, mTimeDependentText=null}, " +
                 "contentDescription=ComplicationText{mSurroundingText=" +
                 "content description, mTimeDependentText=null}), " +
@@ -1538,8 +1771,8 @@
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
             "NoDataComplicationData(placeholder=GoalProgressComplicationData(" +
-                "value=3.4028235E38, targetValue=10000.0, monochromaticImage=null, " +
-                "smallImage=null, title=null, text=ComplicationText{" +
+                "value=3.4028235E38, dynamicValue=null, targetValue=10000.0, " +
+                "monochromaticImage=null, smallImage=null, title=null, text=ComplicationText{" +
                 "mSurroundingText=__placeholder__, mTimeDependentText=null}, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
                 "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
@@ -1722,8 +1955,8 @@
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
             "NoDataComplicationData(placeholder=RangedValueComplicationData(" +
-                "value=3.4028235E38, valueType=1, min=0.0, max=100.0, monochromaticImage=null, " +
-                "smallImage=null, title=null, text=ComplicationText{" +
+                "value=3.4028235E38, dynamicValue=null, valueType=1, min=0.0, max=100.0, " +
+                "monochromaticImage=null, smallImage=null, title=null, text=ComplicationText{" +
                 "mSurroundingText=__placeholder__, mTimeDependentText=null}, " +
                 "contentDescription=ComplicationText{mSurroundingText=" +
                 "content description, mTimeDependentText=null}), " +
@@ -2018,7 +2251,7 @@
     }
 
     @Test
-    public fun rangedValueComplicationData() {
+    public fun rangedValueComplicationData_withFixedValue() {
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
                 .setRangedValue(95f)
@@ -2034,6 +2267,22 @@
     }
 
     @Test
+    public fun rangedValueComplicationData_withDynamicValue() {
+        assertRoundtrip(
+            WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
+                .setRangedDynamicValue(byteArrayOf(42, 107).toDynamicFloat())
+                .setRangedMinValue(0f)
+                .setRangedMaxValue(100f)
+                .setShortTitle(WireComplicationText.plainText("battery"))
+                .setContentDescription(WireComplicationText.plainText("content description"))
+                .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
+                .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
+                .build(),
+            ComplicationType.RANGED_VALUE
+        )
+    }
+
+    @Test
     public fun rangedValueComplicationData_drawSegmented() {
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
@@ -2050,7 +2299,7 @@
     }
 
     @Test
-    public fun goalProgressComplicationData() {
+    public fun goalProgressComplicationData_withFixedValue() {
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_GOAL_PROGRESS)
                 .setRangedValue(1200f)
@@ -2067,6 +2316,23 @@
     }
 
     @Test
+    public fun goalProgressComplicationData_withDynamicValue() {
+        assertRoundtrip(
+            WireComplicationDataBuilder(WireComplicationData.TYPE_GOAL_PROGRESS)
+                .setRangedDynamicValue(byteArrayOf(42, 107).toDynamicFloat())
+                .setTargetValue(10000f)
+                .setShortTitle(WireComplicationText.plainText("steps"))
+                .setContentDescription(WireComplicationText.plainText("content description"))
+                .setColorRamp(intArrayOf(Color.RED, Color.GREEN, Color.BLUE))
+                .setColorRampIsSmoothShaded(false)
+                .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
+                .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
+                .build(),
+            ComplicationType.GOAL_PROGRESS
+        )
+    }
+
+    @Test
     public fun weightedElementsComplicationData() {
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_WEIGHTED_ELEMENTS)
@@ -3006,13 +3272,14 @@
             .setTitle("title".complicationText)
             .build()
 
-        assertThat(data.toString()).isEqualTo("LongTextComplicationData(text=" +
-            "ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, title=" +
-            "ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, " +
-            "monochromaticImage=null, smallImage=null, contentDescription=ComplicationText" +
-            "{mSurroundingText=REDACTED, mTimeDependentText=null}), " +
-            "tapActionLostDueToSerialization=false, tapAction=null, validTimeRange=TimeRange" +
-            "(REDACTED), dataSource=null, persistencePolicy=0, displayPolicy=0)"
+        assertThat(data.toString()).isEqualTo(
+            "LongTextComplicationData(text=" +
+                "ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, title=" +
+                "ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, " +
+                "monochromaticImage=null, smallImage=null, contentDescription=ComplicationText" +
+                "{mSurroundingText=REDACTED, mTimeDependentText=null}), " +
+                "tapActionLostDueToSerialization=false, tapAction=null, validTimeRange=TimeRange" +
+                "(REDACTED), dataSource=null, persistencePolicy=0, displayPolicy=0)"
         )
         assertThat(data.asWireComplicationData().toString()).isEqualTo(
             "ComplicationData{mType=4, mFields=REDACTED}"
@@ -3031,14 +3298,15 @@
             .setTitle("title".complicationText)
             .build()
 
-        assertThat(data.toString()).isEqualTo("RangedValueComplicationData(value=REDACTED, " +
-            "valueType=0, min=0.0, max=100.0, monochromaticImage=null, smallImage=null, " +
-            "title=ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, " +
-            "text=ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, " +
-            "contentDescription=ComplicationText{mSurroundingText=REDACTED, " +
-            "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
-            "tapAction=null, validTimeRange=TimeRange(REDACTED), dataSource=null, " +
-            "colorRamp=null, persistencePolicy=0, displayPolicy=0)"
+        assertThat(data.toString()).isEqualTo(
+            "RangedValueComplicationData(value=REDACTED, dynamicValue=REDACTED, " +
+                "valueType=0, min=0.0, max=100.0, monochromaticImage=null, smallImage=null, " +
+                "title=ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, " +
+                "text=ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, " +
+                "contentDescription=ComplicationText{mSurroundingText=REDACTED, " +
+                "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
+                "tapAction=null, validTimeRange=TimeRange(REDACTED), dataSource=null, " +
+                "colorRamp=null, persistencePolicy=0, displayPolicy=0)"
         )
         assertThat(data.asWireComplicationData().toString()).isEqualTo(
             "ComplicationData{mType=5, mFields=REDACTED}"
@@ -3057,10 +3325,10 @@
             .build()
 
         assertThat(data.toString()).isEqualTo(
-            "GoalProgressComplicationData(value=REDACTED, targetValue=10000.0, " +
-                "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
-                "mSurroundingText=REDACTED, mTimeDependentText=null}, text=null, " +
-                "contentDescription=ComplicationText{mSurroundingText=REDACTED, " +
+            "GoalProgressComplicationData(value=REDACTED, dynamicValue=REDACTED, " +
+                "targetValue=10000.0, monochromaticImage=null, smallImage=null, " +
+                "title=ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, " +
+                "text=null, contentDescription=ComplicationText{mSurroundingText=REDACTED, " +
                 "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
                 "tapAction=null, validTimeRange=TimeRange(REDACTED), dataSource=null, " +
                 "colorRamp=ColorRamp(colors=[-65536, -16711936, -16776961], interpolated=true), " +
diff --git a/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java b/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
index f5f9868..b014c3b 100644
--- a/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
+++ b/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
@@ -429,9 +429,9 @@
     }
 
     private class RangedArcsTestData {
-        public int min;
-        public int max;
-        public int value;
+        public float min;
+        public float max;
+        public float value;
         public float progress;
         public float remaining;
         public float gap;
@@ -498,9 +498,9 @@
     @Test
     public void rangedValueIsDrawnCorrectlyInActiveMode() {
         // GIVEN a complication renderer with ranged value complication data
-        int min = 0;
-        int max = 100;
-        int value = (max - min) / 2;
+        float min = 0;
+        float max = 100;
+        float value = (max - min) / 2;
         mComplicationRenderer.setComplicationData(
                 new ComplicationData.Builder(TYPE_RANGED_VALUE)
                         .setRangedValue(value)
@@ -532,9 +532,9 @@
     @Test
     public void rangedValueIsDrawnCorrectlyInAmbientMode() {
         // GIVEN a complication renderer with ranged value complication data
-        int min = 0;
-        int max = 100;
-        int value = (max - min) / 2;
+        float min = 0;
+        float max = 100;
+        float value = (max - min) / 2;
         mComplicationRenderer.setComplicationData(
                 new ComplicationData.Builder(TYPE_RANGED_VALUE)
                         .setRangedValue(value)
@@ -1005,9 +1005,9 @@
                 new ComplicationData.Builder(TYPE_RANGED_VALUE)
                         .setShortText(ComplicationText.plainText("foo"))
                         .setShortTitle(ComplicationText.plainText("bar"))
-                        .setRangedMinValue(1)
-                        .setRangedValue(5)
-                        .setRangedMaxValue(10)
+                        .setRangedMinValue(1f)
+                        .setRangedValue(5f)
+                        .setRangedMaxValue(10f)
                         .build(),
                 true);
         mComplicationRenderer.setRangedValueProgressHidden(true);
@@ -1029,9 +1029,9 @@
         mComplicationRenderer.setComplicationData(
                 new ComplicationData.Builder(TYPE_RANGED_VALUE)
                         .setIcon(mMockIcon)
-                        .setRangedMinValue(1)
-                        .setRangedValue(5)
-                        .setRangedMaxValue(10)
+                        .setRangedMinValue(1f)
+                        .setRangedValue(5f)
+                        .setRangedMaxValue(10f)
                         .build(),
                 true);
         mComplicationRenderer.setRangedValueProgressHidden(true);
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/WatchFaceServiceAndroidTest.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/WatchFaceServiceAndroidTest.kt
new file mode 100644
index 0000000..2f2f817
--- /dev/null
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/WatchFaceServiceAndroidTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.watchface
+
+import android.content.Context
+import android.graphics.drawable.Icon
+import androidx.test.core.app.ApplicationProvider
+import androidx.wear.watchface.style.UserStyleSchema
+import androidx.wear.watchface.style.UserStyleSetting
+import androidx.wear.watchface.style.WatchFaceLayer
+import androidx.wear.watchface.test.SimpleWatchFaceTestService
+import org.junit.Test
+
+class WatchFaceServiceAndroidTest {
+    @Test
+    fun measuresWatchFaceIconsFromCustomContext() {
+        val context: Context = ApplicationProvider.getApplicationContext()
+        val serviceSpy = object : SimpleWatchFaceTestService() {
+            override val resourcesContext: Context
+                get() = this.createPackageContext(context.packageName,
+                    Context.CONTEXT_RESTRICTED
+                )
+        }
+        val engine = serviceSpy.onCreateEngine() as WatchFaceService.EngineWrapper
+
+        try {
+            val schema = UserStyleSchema(listOf(
+                UserStyleSetting.ListUserStyleSetting(
+                    UserStyleSetting.Id("someId"),
+                    "displayName",
+                    "description",
+                    Icon.createWithResource(
+                        context,
+                        androidx.wear.watchface.test.R.drawable.example_icon_24
+                    ),
+                    listOf(
+                        UserStyleSetting.ListUserStyleSetting.ListOption(
+                            UserStyleSetting.Option.Id("red_style"),
+                            displayName = "Red",
+                            icon = Icon.createWithResource(
+                                context,
+                                androidx.wear.watchface.test.R.drawable.example_icon_24
+                            ),
+                        )
+                    ),
+                    listOf(WatchFaceLayer.BASE)
+                )
+            ))
+
+            // expect no exception
+            engine.validateSchemaWireSize(schema)
+        } finally {
+            engine.onDestroy()
+            serviceSpy.onDestroy()
+        }
+    }
+}
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/SimpleWatchFaceTestService.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/SimpleWatchFaceTestService.kt
new file mode 100644
index 0000000..77c3d9a
--- /dev/null
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/SimpleWatchFaceTestService.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.watchface.test
+
+import android.view.SurfaceHolder
+import androidx.test.core.app.ApplicationProvider
+import androidx.wear.watchface.ComplicationSlotsManager
+import androidx.wear.watchface.WatchFaceService
+import androidx.wear.watchface.WatchState
+import androidx.wear.watchface.style.CurrentUserStyleRepository
+
+/**
+ * A simple WatchFaceService that does not get initialized (because it is PreAndroidR) if there
+ * is no pendingWallpaperInstance. Use it to unit test methods of the EngineWrapper or to spy on it.
+ */
+open class SimpleWatchFaceTestService : WatchFaceService() {
+
+    init {
+        @Suppress("LeakingThis")
+        attachBaseContext(ApplicationProvider.getApplicationContext())
+    }
+
+    override suspend fun createWatchFace(
+        surfaceHolder: SurfaceHolder,
+        watchState: WatchState,
+        complicationSlotsManager: ComplicationSlotsManager,
+        currentUserStyleRepository: CurrentUserStyleRepository
+    ) = throw NotImplementedError("Should not reach this step")
+
+    // Set this to `true` so that the whole setup is skipped for this test
+    override fun isPreAndroidR() = true
+}
diff --git a/wear/watchface/watchface/src/androidTest/res/drawable/example_icon_24.xml b/wear/watchface/watchface/src/androidTest/res/drawable/example_icon_24.xml
new file mode 100644
index 0000000..dc55a00
--- /dev/null
+++ b/wear/watchface/watchface/src/androidTest/res/drawable/example_icon_24.xml
@@ -0,0 +1,21 @@
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector android:height="24dp" android:tint="#000000"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2z"/>
+</vector>
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 073f8a4..5e8f737 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -424,6 +424,14 @@
     }
 
     /**
+     * The context used to resolve resources. Unlocks future work.
+     * @hide
+     */
+    protected open val resourcesContext: Context
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        get() = this
+
+    /**
      * Returns the id of the XmlSchemaAndComplicationSlotsDefinition XML resource or 0 if it can't
      * be found.
      *
@@ -2478,7 +2486,7 @@
             @Suppress("Deprecation") // userStyleSettings
             for (styleSetting in schema.userStyleSettings) {
                 estimatedBytes += styleSetting.estimateWireSizeInBytesAndValidateIconDimensions(
-                    _context,
+                    resourcesContext,
                     MAX_REASONABLE_SCHEMA_ICON_WIDTH,
                     MAX_REASONABLE_SCHEMA_ICON_HEIGHT,
                 )
diff --git a/wear/wear-input/src/main/res/values-en-rCA/strings.xml b/wear/wear-input/src/main/res/values-en-rCA/strings.xml
index 8d28dbe..eb6c4b0 100644
--- a/wear/wear-input/src/main/res/values-en-rCA/strings.xml
+++ b/wear/wear-input/src/main/res/values-en-rCA/strings.xml
@@ -3,11 +3,11 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="buttons_round_top_center" msgid="5559087816427379703">"Top"</string>
     <string name="buttons_round_top_right" msgid="4871780374047139328">"Top right"</string>
-    <string name="buttons_round_center_right" msgid="1509579668330531551">"Centre right"</string>
+    <string name="buttons_round_center_right" msgid="1509579668330531551">"Center right"</string>
     <string name="buttons_round_bottom_right" msgid="2377403938095293895">"Bottom right"</string>
     <string name="buttons_round_bottom_center" msgid="7330585893633364324">"Bottom"</string>
     <string name="buttons_round_bottom_left" msgid="2128239325315047486">"Bottom left"</string>
-    <string name="buttons_round_center_left" msgid="4851687456301428363">"Centre left"</string>
+    <string name="buttons_round_center_left" msgid="4851687456301428363">"Center left"</string>
     <string name="buttons_round_top_left" msgid="6011962751086201079">"Top left"</string>
     <string name="buttons_round_top_right_upper" msgid="5489914740902966147">"Top right, upper"</string>
     <string name="buttons_round_top_right_lower" msgid="49744844591556337">"Top right, lower"</string>
@@ -18,10 +18,10 @@
     <string name="buttons_round_top_left_lower" msgid="4749025344320804614">"Top left, lower"</string>
     <string name="buttons_round_top_left_upper" msgid="2546930425604677415">"Top left, upper"</string>
     <string name="buttons_rect_left_top" msgid="5658240954637179416">"Top, left side"</string>
-    <string name="buttons_rect_left_center" msgid="2090348335774325845">"Centre left"</string>
+    <string name="buttons_rect_left_center" msgid="2090348335774325845">"Center left"</string>
     <string name="buttons_rect_left_bottom" msgid="2890382227404669440">"Bottom, left side"</string>
     <string name="buttons_rect_right_top" msgid="8781047384037217975">"Top, right side"</string>
-    <string name="buttons_rect_right_center" msgid="9046922334870476704">"Centre right"</string>
+    <string name="buttons_rect_right_center" msgid="9046922334870476704">"Center right"</string>
     <string name="buttons_rect_right_bottom" msgid="231110280868923853">"Bottom, right side"</string>
     <string name="buttons_rect_top_left" msgid="7572206729575579647">"Top left"</string>
     <string name="buttons_rect_top_center" msgid="79705740801041383">"Top"</string>
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt b/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt
index dde6316..36cefde 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt
@@ -27,7 +27,7 @@
 class ExampleWindowInitializer : Initializer<SplitController> {
     override fun create(context: Context): SplitController {
         SplitController.initialize(context, R.xml.main_split_config)
-        return SplitController.getInstance()
+        return SplitController.getInstance(context)
     }
 
     override fun dependencies(): List<Class<out Initializer<*>>> {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java
index 29f6ce3..abe27d6 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java
+++ b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java
@@ -23,9 +23,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
-import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.TypedValue;
 import android.view.View;
 import android.widget.CompoundButton;
 
@@ -56,7 +54,7 @@
         implements CompoundButton.OnCheckedChangeListener {
 
     private static final String TAG = "SplitActivityTest";
-    private static final float MIN_SPLIT_WIDTH_DP = 600f;
+    static final int MIN_SPLIT_WIDTH_DP = 600;
     static final float SPLIT_RATIO = 0.3f;
     static final String EXTRA_LAUNCH_C_TO_SIDE = "launch_c_to_side";
 
@@ -105,7 +103,7 @@
         mViewBinding.fullscreenECheckBox.setOnCheckedChangeListener(this);
         mViewBinding.splitWithFCheckBox.setOnCheckedChangeListener(this);
 
-        mSplitController = SplitController.Companion.getInstance();
+        mSplitController = SplitController.getInstance(this);
     }
 
     @Override
@@ -255,17 +253,14 @@
 
     /** Updates the split rules based on the current selection on checkboxes. */
     private void updateRulesFromCheckboxes() {
-        int minSplitWidth = minSplitWidth();
         mSplitController.clearRegisteredRules();
 
         Set<SplitPairFilter> pairFilters = new HashSet<>();
         pairFilters.add(new SplitPairFilter(componentName(SplitActivityA.class),
                 componentName("*"), null));
-        SplitPairRule rule = new SplitPairRule.Builder(
-                pairFilters,
-                minSplitWidth,
-                0 /* minSmallestWidth */
-        )
+        SplitPairRule rule = new SplitPairRule.Builder(pairFilters)
+                .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
+                .setMinSmallestWidthDp(0)
                 .setFinishPrimaryWithSecondary(SplitRule.FINISH_NEVER)
                 .setFinishSecondaryWithPrimary(SplitRule.FINISH_NEVER)
                 .setClearTop(true)
@@ -282,10 +277,10 @@
                 componentName("androidx.window.sample.embedding.SplitActivityPlaceholder"));
         SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule.Builder(
                 activityFilters,
-                intent,
-                minSplitWidth,
-                0 /* minSmallestWidth */
+                intent
         )
+                .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
+                .setMinSmallestWidthDp(0)
                 .setSticky(mViewBinding.useStickyPlaceholderCheckBox.isChecked())
                 .setFinishPrimaryWithPlaceholder(SplitRule.FINISH_ADJACENT)
                 .setSplitRatio(SPLIT_RATIO)
@@ -297,11 +292,9 @@
         pairFilters = new HashSet<>();
         pairFilters.add(new SplitPairFilter(componentName(SplitActivityB.class),
                 componentName(SplitActivityC.class), null));
-        rule = new SplitPairRule.Builder(
-                pairFilters,
-                minSplitWidth,
-                0 /* minSmallestWidth */
-        )
+        rule = new SplitPairRule.Builder(pairFilters)
+                .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
+                .setMinSmallestWidthDp(0)
                 .setFinishPrimaryWithSecondary(
                         mViewBinding.finishBCCheckBox.isChecked()
                                 ? SplitRule.FINISH_ALWAYS : SplitRule.FINISH_NEVER
@@ -320,11 +313,9 @@
         pairFilters = new HashSet<>();
         pairFilters.add(new SplitPairFilter(componentName("androidx.window.*"),
                 componentName(SplitActivityF.class), null));
-        rule = new SplitPairRule.Builder(
-                pairFilters,
-                minSplitWidth,
-                0 /* minSmallestWidth */
-        )
+        rule = new SplitPairRule.Builder(pairFilters)
+                .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
+                .setMinSmallestWidthDp(0)
                 .setFinishPrimaryWithSecondary(SplitRule.FINISH_NEVER)
                 .setFinishSecondaryWithPrimary(SplitRule.FINISH_NEVER)
                 .setClearTop(true)
@@ -353,11 +344,6 @@
         return new ComponentName(getPackageName(), className);
     }
 
-    int minSplitWidth() {
-        DisplayMetrics dm = getResources().getDisplayMetrics();
-        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, MIN_SPLIT_WIDTH_DP, dm);
-    }
-
     /** Updates the status label that says when an activity is embedded. */
     void updateEmbeddedStatus() {
         if (mSplitController.isActivityEmbedded(this)) {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt
index 5ca237a..aa282c7 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt
@@ -39,7 +39,7 @@
         findViewById<View>(R.id.root_split_activity_layout)
             .setBackgroundColor(Color.parseColor("#e0f7fa"))
 
-        splitController = SplitController.getInstance()
+        splitController = SplitController.getInstance(this)
     }
 
     open fun onItemClick(view: View) {
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt
index acb6565..084b23f 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt
@@ -35,15 +35,13 @@
         val placeholderIntent = Intent()
         placeholderIntent.component =
             componentName("androidx.window.sample.embedding.SplitActivityPlaceholder")
-        val placeholderRule = SplitPlaceholderRule.Builder(
-            activityFilters,
-            placeholderIntent,
-            minWidth = minSplitWidth(),
-            minSmallestWidth = 0)
+        val placeholderRule = SplitPlaceholderRule.Builder(activityFilters, placeholderIntent)
+            .setMinWidthDp(MIN_SPLIT_WIDTH_DP)
+            .setMinSmallestWidthDp(0)
             .setFinishPrimaryWithPlaceholder(FINISH_ADJACENT)
             .setSplitRatio(SPLIT_RATIO)
             .build()
-        SplitController.getInstance().registerRule(placeholderRule)
+        SplitController.getInstance(this).registerRule(placeholderRule)
         val activityIntent = Intent()
         activityIntent.component = componentName(
             "androidx.window.sample.embedding.SplitActivityTrampolineTarget")
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt
index 6b7c99f..efbfe5d 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt
@@ -73,7 +73,7 @@
         componentNamePlaceholder = ComponentName(packageName,
             SplitPipActivityPlaceholder::class.java.name)
 
-        splitController = SplitController.getInstance()
+        splitController = SplitController.getInstance(this)
 
         // Buttons for split rules of the main activity.
         viewBinding.splitMainCheckBox.setOnCheckedChangeListener(this)
@@ -240,7 +240,9 @@
             pairFilters.add(SplitPairFilter(componentNameA, componentNameNotPip, null))
             val finishAWithB = viewBinding.finishPrimaryWithSecondaryCheckBox.isChecked
             val finishBWithA = viewBinding.finishSecondaryWithPrimaryCheckBox.isChecked
-            val rule = SplitPairRule.Builder(pairFilters, 0, 0)
+            val rule = SplitPairRule.Builder(pairFilters)
+                .setMinWidthDp(0)
+                .setMinSmallestWidthDp(0)
                 .setFinishPrimaryWithSecondary(
                     if (finishAWithB) SplitRule.FINISH_ALWAYS else SplitRule.FINISH_NEVER)
                 .setFinishSecondaryWithPrimary(
@@ -256,7 +258,9 @@
             activityFilters.add(ActivityFilter(componentNameB, null))
             val intent = Intent().setComponent(componentNamePlaceholder)
             val isSticky = viewBinding.useStickyPlaceHolderCheckBox.isChecked
-            val rule = SplitPlaceholderRule.Builder(activityFilters, intent, 0, 0)
+            val rule = SplitPlaceholderRule.Builder(activityFilters, intent)
+                .setMinWidthDp(0)
+                .setMinSmallestWidthDp(0)
                 .setSticky(isSticky)
                 .setFinishPrimaryWithPlaceholder(SplitRule.FINISH_ADJACENT)
                 .setSplitRatio(splitRatio)
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index 2e7de91..8a7492f 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -46,7 +46,7 @@
   public final class SplitController {
     method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
     method public void clearRegisteredRules();
-    method public static androidx.window.embedding.SplitController getInstance();
+    method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
     method public java.util.Set<androidx.window.embedding.EmbeddingRule> getSplitRules();
     method public static void initialize(android.content.Context context, int staticRuleResourceId);
     method public boolean isActivityEmbedded(android.app.Activity activity);
@@ -58,7 +58,7 @@
   }
 
   public static final class SplitController.Companion {
-    method public androidx.window.embedding.SplitController getInstance();
+    method public androidx.window.embedding.SplitController getInstance(android.content.Context context);
     method public void initialize(android.content.Context context, int staticRuleResourceId);
   }
 
@@ -96,12 +96,14 @@
   }
 
   public static final class SplitPairRule.Builder {
-    ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth);
+    ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters);
     method public androidx.window.embedding.SplitPairRule build();
     method public androidx.window.embedding.SplitPairRule.Builder setClearTop(boolean clearTop);
     method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
     method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
     method public androidx.window.embedding.SplitPairRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
+    method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
   }
 
@@ -117,24 +119,27 @@
   }
 
   public static final class SplitPlaceholderRule.Builder {
-    ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth);
+    ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent);
     method public androidx.window.embedding.SplitPlaceholderRule build();
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
   }
 
   public class SplitRule extends androidx.window.embedding.EmbeddingRule {
     method public final int getLayoutDirection();
-    method public final int getMinSmallestWidth();
-    method public final int getMinWidth();
+    method public final int getMinSmallestWidthDp();
+    method public final int getMinWidthDp();
     method public final float getSplitRatio();
     property public final int layoutDirection;
-    property public final int minSmallestWidth;
-    property public final int minWidth;
+    property public final int minSmallestWidthDp;
+    property public final int minWidthDp;
     property public final float splitRatio;
     field public static final androidx.window.embedding.SplitRule.Companion Companion;
+    field public static final int DEFAULT_SPLIT_MIN_DIMENSION_DP = 600; // 0x258
     field public static final int FINISH_ADJACENT = 2; // 0x2
     field public static final int FINISH_ALWAYS = 1; // 0x1
     field public static final int FINISH_NEVER = 0; // 0x0
diff --git a/window/window/api/public_plus_experimental_current.txt b/window/window/api/public_plus_experimental_current.txt
index 361f795..c9c3113 100644
--- a/window/window/api/public_plus_experimental_current.txt
+++ b/window/window/api/public_plus_experimental_current.txt
@@ -53,7 +53,7 @@
   public final class SplitController {
     method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
     method public void clearRegisteredRules();
-    method public static androidx.window.embedding.SplitController getInstance();
+    method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
     method public java.util.Set<androidx.window.embedding.EmbeddingRule> getSplitRules();
     method public static void initialize(android.content.Context context, int staticRuleResourceId);
     method public boolean isActivityEmbedded(android.app.Activity activity);
@@ -65,7 +65,7 @@
   }
 
   public static final class SplitController.Companion {
-    method public androidx.window.embedding.SplitController getInstance();
+    method public androidx.window.embedding.SplitController getInstance(android.content.Context context);
     method public void initialize(android.content.Context context, int staticRuleResourceId);
   }
 
@@ -103,12 +103,14 @@
   }
 
   public static final class SplitPairRule.Builder {
-    ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth);
+    ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters);
     method public androidx.window.embedding.SplitPairRule build();
     method public androidx.window.embedding.SplitPairRule.Builder setClearTop(boolean clearTop);
     method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
     method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
     method public androidx.window.embedding.SplitPairRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
+    method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
   }
 
@@ -124,24 +126,27 @@
   }
 
   public static final class SplitPlaceholderRule.Builder {
-    ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth);
+    ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent);
     method public androidx.window.embedding.SplitPlaceholderRule build();
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
   }
 
   public class SplitRule extends androidx.window.embedding.EmbeddingRule {
     method public final int getLayoutDirection();
-    method public final int getMinSmallestWidth();
-    method public final int getMinWidth();
+    method public final int getMinSmallestWidthDp();
+    method public final int getMinWidthDp();
     method public final float getSplitRatio();
     property public final int layoutDirection;
-    property public final int minSmallestWidth;
-    property public final int minWidth;
+    property public final int minSmallestWidthDp;
+    property public final int minWidthDp;
     property public final float splitRatio;
     field public static final androidx.window.embedding.SplitRule.Companion Companion;
+    field public static final int DEFAULT_SPLIT_MIN_DIMENSION_DP = 600; // 0x258
     field public static final int FINISH_ADJACENT = 2; // 0x2
     field public static final int FINISH_ALWAYS = 1; // 0x1
     field public static final int FINISH_NEVER = 0; // 0x0
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index 2e7de91..8a7492f 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -46,7 +46,7 @@
   public final class SplitController {
     method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
     method public void clearRegisteredRules();
-    method public static androidx.window.embedding.SplitController getInstance();
+    method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
     method public java.util.Set<androidx.window.embedding.EmbeddingRule> getSplitRules();
     method public static void initialize(android.content.Context context, int staticRuleResourceId);
     method public boolean isActivityEmbedded(android.app.Activity activity);
@@ -58,7 +58,7 @@
   }
 
   public static final class SplitController.Companion {
-    method public androidx.window.embedding.SplitController getInstance();
+    method public androidx.window.embedding.SplitController getInstance(android.content.Context context);
     method public void initialize(android.content.Context context, int staticRuleResourceId);
   }
 
@@ -96,12 +96,14 @@
   }
 
   public static final class SplitPairRule.Builder {
-    ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth);
+    ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters);
     method public androidx.window.embedding.SplitPairRule build();
     method public androidx.window.embedding.SplitPairRule.Builder setClearTop(boolean clearTop);
     method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
     method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
     method public androidx.window.embedding.SplitPairRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
+    method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
   }
 
@@ -117,24 +119,27 @@
   }
 
   public static final class SplitPlaceholderRule.Builder {
-    ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth);
+    ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent);
     method public androidx.window.embedding.SplitPlaceholderRule build();
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
   }
 
   public class SplitRule extends androidx.window.embedding.EmbeddingRule {
     method public final int getLayoutDirection();
-    method public final int getMinSmallestWidth();
-    method public final int getMinWidth();
+    method public final int getMinSmallestWidthDp();
+    method public final int getMinWidthDp();
     method public final float getSplitRatio();
     property public final int layoutDirection;
-    property public final int minSmallestWidth;
-    property public final int minWidth;
+    property public final int minSmallestWidthDp;
+    property public final int minWidthDp;
     property public final float splitRatio;
     field public static final androidx.window.embedding.SplitRule.Companion Companion;
+    field public static final int DEFAULT_SPLIT_MIN_DIMENSION_DP = 600; // 0x258
     field public static final int FINISH_ADJACENT = 2; // 0x2
     field public static final int FINISH_ALWAYS = 1; // 0x1
     field public static final int FINISH_NEVER = 0; // 0x0
diff --git a/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt b/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
index 164c867..2aa3ff8 100644
--- a/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
+++ b/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
@@ -16,13 +16,14 @@
 
 package androidx.window.embedding
 
-import android.app.Application
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
-import android.content.res.Resources
 import android.graphics.Rect
+import android.util.DisplayMetrics
 import android.util.LayoutDirection
 import androidx.test.core.app.ApplicationProvider
+import androidx.window.embedding.SplitRule.Companion.DEFAULT_SPLIT_MIN_DIMENSION_DP
 import androidx.window.embedding.SplitRule.Companion.FINISH_ADJACENT
 import androidx.window.embedding.SplitRule.Companion.FINISH_ALWAYS
 import androidx.window.embedding.SplitRule.Companion.FINISH_NEVER
@@ -31,6 +32,7 @@
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertThrows
 import org.junit.Assert.assertTrue
+import org.junit.Before
 import org.junit.Test
 
 /**
@@ -40,15 +42,23 @@
  * @see ActivityRule
  */
 class EmbeddingRuleConstructionTests {
+    private lateinit var application: Context
+    private lateinit var displayMetrics: DisplayMetrics
+
+    @Before
+    fun setUp() {
+        application = ApplicationProvider.getApplicationContext()
+        displayMetrics = application.resources.displayMetrics
+    }
+
     /**
      * Verifies that default params are set correctly when reading {@link SplitPairRule} from XML.
      */
     @Test
     fun testDefaults_SplitPairRule_Xml() {
-        SplitController.initialize(ApplicationProvider.getApplicationContext(),
-            R.xml.test_split_config_default_split_pair_rule)
+        SplitController.initialize(application, R.xml.test_split_config_default_split_pair_rule)
 
-        val rules = SplitController.getInstance().getSplitRules()
+        val rules = SplitController.getInstance(application).getSplitRules()
         assertEquals(1, rules.size)
         val rule: SplitPairRule = rules.first() as SplitPairRule
         assertEquals(FINISH_NEVER, rule.finishPrimaryWithSecondary)
@@ -56,9 +66,8 @@
         assertEquals(false, rule.clearTop)
         assertEquals(0.5f, rule.splitRatio)
         assertEquals(LayoutDirection.LOCALE, rule.layoutDirection)
-        val application = ApplicationProvider.getApplicationContext<Application>()
-        assertTrue(rule.checkParentBounds(minValidWindowBounds(application.resources)))
-        assertFalse(rule.checkParentBounds(almostValidWindowBounds(application.resources)))
+        assertTrue(rule.checkParentBounds(displayMetrics.density, minValidWindowBounds()))
+        assertFalse(rule.checkParentBounds(displayMetrics.density, almostValidWindowBounds()))
     }
 
     /**
@@ -67,20 +76,14 @@
      */
     @Test
     fun testDefaults_SplitPairRule_Builder() {
-        val application = ApplicationProvider.getApplicationContext<Application>()
-        val bounds = minValidWindowBounds(application.resources)
-        val rule = SplitPairRule.Builder(
-            HashSet(),
-            bounds.width(),
-            bounds.width()
-        ).build()
+        val rule = SplitPairRule.Builder(HashSet()).build()
         assertEquals(FINISH_NEVER, rule.finishPrimaryWithSecondary)
         assertEquals(FINISH_ALWAYS, rule.finishSecondaryWithPrimary)
         assertEquals(false, rule.clearTop)
         assertEquals(0.5f, rule.splitRatio)
         assertEquals(LayoutDirection.LOCALE, rule.layoutDirection)
-        assertTrue(rule.checkParentBounds(minValidWindowBounds(application.resources)))
-        assertFalse(rule.checkParentBounds(almostValidWindowBounds(application.resources)))
+        assertTrue(rule.checkParentBounds(displayMetrics.density, minValidWindowBounds()))
+        assertFalse(rule.checkParentBounds(displayMetrics.density, almostValidWindowBounds()))
     }
 
     /**
@@ -97,11 +100,9 @@
                 "ACTION"
             )
         )
-        val rule = SplitPairRule.Builder(
-            filters,
-            123,
-            456
-        )
+        val rule = SplitPairRule.Builder(filters)
+            .setMinWidthDp(123)
+            .setMinSmallestWidthDp(456)
             .setFinishPrimaryWithSecondary(FINISH_ADJACENT)
             .setFinishSecondaryWithPrimary(FINISH_ADJACENT)
             .setClearTop(true)
@@ -114,8 +115,8 @@
         assertEquals(0.3f, rule.splitRatio)
         assertEquals(LayoutDirection.LTR, rule.layoutDirection)
         assertEquals(filters, rule.filters)
-        assertEquals(123, rule.minWidth)
-        assertEquals(456, rule.minSmallestWidth)
+        assertEquals(123, rule.minWidthDp)
+        assertEquals(456, rule.minSmallestWidthDp)
     }
 
     /**
@@ -125,34 +126,28 @@
     @Test
     fun test_SplitPairRule_Builder_illegalArguments() {
         assertThrows(IllegalArgumentException::class.java) {
-            SplitPairRule.Builder(
-                HashSet(),
-                minWidth = -1,
-                minSmallestWidth = 456
-            ).build()
+            SplitPairRule.Builder(HashSet())
+                .setMinWidthDp(-1)
+                .setMinSmallestWidthDp(456)
+                .build()
         }
         assertThrows(IllegalArgumentException::class.java) {
-            SplitPairRule.Builder(
-                HashSet(),
-                minWidth = 123,
-                minSmallestWidth = -1
-            ).build()
+            SplitPairRule.Builder(HashSet())
+                .setMinWidthDp(123)
+                .setMinSmallestWidthDp(-1)
+                .build()
         }
         assertThrows(IllegalArgumentException::class.java) {
-            SplitPairRule.Builder(
-                HashSet(),
-                minWidth = 123,
-                minSmallestWidth = 456
-            )
+            SplitPairRule.Builder(HashSet())
+                .setMinWidthDp(123)
+                .setMinSmallestWidthDp(456)
                 .setSplitRatio(-1.0f)
                 .build()
         }
         assertThrows(IllegalArgumentException::class.java) {
-            SplitPairRule.Builder(
-                HashSet(),
-                minWidth = 123,
-                minSmallestWidth = 456
-            )
+            SplitPairRule.Builder(HashSet())
+                .setMinWidthDp(123)
+                .setMinSmallestWidthDp(456)
                 .setSplitRatio(1.1f)
                 .build()
         }
@@ -164,19 +159,18 @@
      */
     @Test
     fun testDefaults_SplitPlaceholderRule_Xml() {
-        SplitController.initialize(ApplicationProvider.getApplicationContext(),
+        SplitController.initialize(application,
             R.xml.test_split_config_default_split_placeholder_rule)
 
-        val rules = SplitController.getInstance().getSplitRules()
+        val rules = SplitController.getInstance(application).getSplitRules()
         assertEquals(1, rules.size)
         val rule: SplitPlaceholderRule = rules.first() as SplitPlaceholderRule
         assertEquals(FINISH_ALWAYS, rule.finishPrimaryWithPlaceholder)
         assertEquals(false, rule.isSticky)
         assertEquals(0.5f, rule.splitRatio)
         assertEquals(LayoutDirection.LOCALE, rule.layoutDirection)
-        val application = ApplicationProvider.getApplicationContext<Application>()
-        assertTrue(rule.checkParentBounds(minValidWindowBounds(application.resources)))
-        assertFalse(rule.checkParentBounds(almostValidWindowBounds(application.resources)))
+        assertTrue(rule.checkParentBounds(displayMetrics.density, minValidWindowBounds()))
+        assertFalse(rule.checkParentBounds(displayMetrics.density, almostValidWindowBounds()))
     }
 
     /**
@@ -185,12 +179,9 @@
      */
     @Test
     fun testDefaults_SplitPlaceholderRule_Builder() {
-        val rule = SplitPlaceholderRule.Builder(
-            HashSet(),
-            Intent(),
-            123,
-            456
-        )
+        val rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
+            .setMinWidthDp(123)
+            .setMinSmallestWidthDp(456)
             .build()
         assertEquals(FINISH_ALWAYS, rule.finishPrimaryWithPlaceholder)
         assertEquals(false, rule.isSticky)
@@ -212,12 +203,9 @@
             )
         )
         val intent = Intent("ACTION")
-        val rule = SplitPlaceholderRule.Builder(
-            filters,
-            intent,
-            123,
-            456
-        )
+        val rule = SplitPlaceholderRule.Builder(filters, intent)
+            .setMinWidthDp(123)
+            .setMinSmallestWidthDp(456)
             .setFinishPrimaryWithPlaceholder(FINISH_ADJACENT)
             .setSticky(true)
             .setSplitRatio(0.3f)
@@ -229,8 +217,8 @@
         assertEquals(LayoutDirection.LTR, rule.layoutDirection)
         assertEquals(filters, rule.filters)
         assertEquals(intent, rule.placeholderIntent)
-        assertEquals(123, rule.minWidth)
-        assertEquals(456, rule.minSmallestWidth)
+        assertEquals(123, rule.minWidthDp)
+        assertEquals(456, rule.minSmallestWidthDp)
     }
 
     /**
@@ -240,48 +228,35 @@
     @Test
     fun test_SplitPlaceholderRule_Builder_illegalArguments() {
         assertThrows(IllegalArgumentException::class.java) {
-            SplitPlaceholderRule.Builder(
-                HashSet(),
-                Intent(),
-                minWidth = -1,
-                minSmallestWidth = 456
-            ).build()
+            SplitPlaceholderRule.Builder(HashSet(), Intent())
+                .setMinWidthDp(-1)
+                .setMinSmallestWidthDp(456)
+                .build()
         }
         assertThrows(IllegalArgumentException::class.java) {
-            SplitPlaceholderRule.Builder(
-                HashSet(),
-                Intent(),
-                minWidth = 123,
-                minSmallestWidth = -1
-            ).build()
+            SplitPlaceholderRule.Builder(HashSet(), Intent())
+                .setMinWidthDp(123)
+                .setMinSmallestWidthDp(-1)
+                .build()
         }
         assertThrows(IllegalArgumentException::class.java) {
-            SplitPlaceholderRule.Builder(
-                HashSet(),
-                Intent(),
-                minWidth = 123,
-                minSmallestWidth = 456
-            )
+            SplitPlaceholderRule.Builder(HashSet(), Intent())
+                .setMinWidthDp(123)
+                .setMinSmallestWidthDp(456)
                 .setFinishPrimaryWithPlaceholder(FINISH_NEVER)
                 .build()
         }
         assertThrows(IllegalArgumentException::class.java) {
-            SplitPlaceholderRule.Builder(
-                HashSet(),
-                Intent(),
-                minWidth = 123,
-                minSmallestWidth = 456
-            )
+            SplitPlaceholderRule.Builder(HashSet(), Intent())
+                .setMinWidthDp(123)
+                .setMinSmallestWidthDp(456)
                 .setSplitRatio(-1.0f)
                 .build()
         }
         assertThrows(IllegalArgumentException::class.java) {
-            SplitPlaceholderRule.Builder(
-                HashSet(),
-                Intent(),
-                minWidth = 123,
-                minSmallestWidth = 456
-            )
+            SplitPlaceholderRule.Builder(HashSet(), Intent())
+                .setMinWidthDp(123)
+                .setMinSmallestWidthDp(456)
                 .setSplitRatio(1.1f)
                 .build()
         }
@@ -292,10 +267,9 @@
      */
     @Test
     fun testDefaults_ActivityRule_Xml() {
-        SplitController.initialize(ApplicationProvider.getApplicationContext(),
-            R.xml.test_split_config_default_activity_rule)
+        SplitController.initialize(application, R.xml.test_split_config_default_activity_rule)
 
-        val rules = SplitController.getInstance().getSplitRules()
+        val rules = SplitController.getInstance(application).getSplitRules()
         assertEquals(1, rules.size)
         val rule: ActivityRule = rules.first() as ActivityRule
         assertEquals(false, rule.alwaysExpand)
@@ -330,22 +304,20 @@
         assertEquals(filters, rule.filters)
     }
 
-    private fun minValidWindowBounds(resources: Resources): Rect {
-        val minValidWidthDp = 600
+    private fun minValidWindowBounds(): Rect {
         // Get the screen's density scale
-        val scale: Float = resources.displayMetrics.density
+        val scale: Float = displayMetrics.density
         // Convert the dps to pixels, based on density scale
-        val minValidWidthPx = (minValidWidthDp * scale + 0.5f).toInt()
+        val minValidWidthPx = (DEFAULT_SPLIT_MIN_DIMENSION_DP * scale + 0.5f).toInt()
 
         return Rect(0, 0, minValidWidthPx, minValidWidthPx)
     }
 
-    private fun almostValidWindowBounds(resources: Resources): Rect {
-        val minValidWidthDp = 600
+    private fun almostValidWindowBounds(): Rect {
         // Get the screen's density scale
-        val scale: Float = resources.displayMetrics.density
+        val scale: Float = displayMetrics.density
         // Convert the dps to pixels, based on density scale
-        val minValidWidthPx = ((minValidWidthDp - 1) * scale + 0.5f).toInt()
+        val minValidWidthPx = ((DEFAULT_SPLIT_MIN_DIMENSION_DP) - 1 * scale + 0.5f).toInt()
 
         return Rect(0, 0, minValidWidthPx, minValidWidthPx)
     }
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
index a845609..1700d8d 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
@@ -16,10 +16,6 @@
 
 package androidx.window.embedding
 
-import android.annotation.SuppressLint
-import android.app.Activity
-import android.content.Intent
-import android.view.WindowMetrics
 import androidx.window.extensions.embedding.ActivityRule as OEMActivityRule
 import androidx.window.extensions.embedding.ActivityRule.Builder as ActivityRuleBuilder
 import androidx.window.extensions.embedding.EmbeddingRule as OEMEmbeddingRule
@@ -28,6 +24,11 @@
 import androidx.window.extensions.embedding.SplitPairRule.Builder as SplitPairRuleBuilder
 import androidx.window.extensions.embedding.SplitPlaceholderRule as OEMSplitPlaceholderRule
 import androidx.window.extensions.embedding.SplitPlaceholderRule.Builder as SplitPlaceholderRuleBuilder
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.view.WindowMetrics
 import androidx.window.core.PredicateAdapter
 import androidx.window.extensions.WindowExtensionsProvider
 
@@ -89,9 +90,9 @@
     }
 
     @SuppressLint("ClassVerificationFailure", "NewApi")
-    private fun translateParentMetricsPredicate(splitRule: SplitRule): Any {
+    private fun translateParentMetricsPredicate(context: Context, splitRule: SplitRule): Any {
         return predicateAdapter.buildPredicate(WindowMetrics::class) { windowMetrics ->
-            splitRule.checkParentMetrics(windowMetrics)
+            splitRule.checkParentMetrics(context, windowMetrics)
         }
     }
 
@@ -111,6 +112,7 @@
 
     @SuppressLint("WrongConstant") // Converting from Jetpack to Extensions constants
     private fun translateSplitPairRule(
+        context: Context,
         rule: SplitPairRule,
         predicateClass: Class<*>
     ): OEMSplitPairRule {
@@ -121,7 +123,7 @@
         ).newInstance(
             translateActivityPairPredicates(rule.filters),
             translateActivityIntentPredicates(rule.filters),
-            translateParentMetricsPredicate(rule)
+            translateParentMetricsPredicate(context, rule)
         )
             .setSplitRatio(rule.splitRatio)
             .setLayoutDirection(rule.layoutDirection)
@@ -133,6 +135,7 @@
 
     @SuppressLint("WrongConstant") // Converting from Jetpack to Extensions constants
     private fun translateSplitPlaceholderRule(
+        context: Context,
         rule: SplitPlaceholderRule,
         predicateClass: Class<*>
     ): OEMSplitPlaceholderRule {
@@ -145,7 +148,7 @@
             rule.placeholderIntent,
             translateActivityPredicates(rule.filters),
             translateIntentPredicates(rule.filters),
-            translateParentMetricsPredicate(rule)
+            translateParentMetricsPredicate(context, rule)
         )
             .setSplitRatio(rule.splitRatio)
             .setLayoutDirection(rule.layoutDirection)
@@ -184,12 +187,13 @@
             .build()
     }
 
-    fun translate(rules: Set<EmbeddingRule>): Set<OEMEmbeddingRule> {
+    fun translate(context: Context, rules: Set<EmbeddingRule>): Set<OEMEmbeddingRule> {
         val predicateClass = predicateAdapter.predicateClassOrNull() ?: return emptySet()
         return rules.map { rule ->
             when (rule) {
-                is SplitPairRule -> translateSplitPairRule(rule, predicateClass)
-                is SplitPlaceholderRule -> translateSplitPlaceholderRule(rule, predicateClass)
+                is SplitPairRule -> translateSplitPairRule(context, rule, predicateClass)
+                is SplitPlaceholderRule ->
+                    translateSplitPlaceholderRule(context, rule, predicateClass)
                 is ActivityRule -> translateActivityRule(rule, predicateClass)
                 else -> throw IllegalArgumentException("Unsupported rule type")
             }
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
index 85993d6..52f2974 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
@@ -18,6 +18,7 @@
 
 import androidx.window.extensions.embedding.SplitInfo as OEMSplitInfo
 import android.app.Activity
+import android.content.Context
 import android.util.Log
 import androidx.window.core.ConsumerAdapter
 import androidx.window.embedding.EmbeddingInterfaceCompat.EmbeddingCallbackInterface
@@ -32,11 +33,12 @@
 internal class EmbeddingCompat constructor(
     private val embeddingExtension: ActivityEmbeddingComponent,
     private val adapter: EmbeddingAdapter,
-    private val consumerAdapter: ConsumerAdapter
+    private val consumerAdapter: ConsumerAdapter,
+    private val applicationContext: Context
 ) : EmbeddingInterfaceCompat {
 
     override fun setSplitRules(rules: Set<EmbeddingRule>) {
-        val r = adapter.translate(rules)
+        val r = adapter.translate(applicationContext, rules)
         embeddingExtension.setEmbeddingRules(r)
     }
 
diff --git a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
index 5c2d3c1..a2ff243 100644
--- a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
@@ -17,6 +17,7 @@
 package androidx.window.embedding
 
 import android.app.Activity
+import android.content.Context
 import android.util.Log
 import androidx.annotation.GuardedBy
 import androidx.annotation.VisibleForTesting
@@ -52,11 +53,11 @@
         private val globalLock = ReentrantLock()
         private const val TAG = "EmbeddingBackend"
 
-        fun getInstance(): ExtensionEmbeddingBackend {
+        fun getInstance(applicationContext: Context): ExtensionEmbeddingBackend {
             if (globalInstance == null) {
                 globalLock.withLock {
                     if (globalInstance == null) {
-                        val embeddingExtension = initAndVerifyEmbeddingExtension()
+                        val embeddingExtension = initAndVerifyEmbeddingExtension(applicationContext)
                         globalInstance = ExtensionEmbeddingBackend(embeddingExtension)
                     }
                 }
@@ -69,7 +70,9 @@
          * implemented by OEM if available on this device. This also verifies if the loaded
          * implementation conforms to the declared API version.
          */
-        private fun initAndVerifyEmbeddingExtension(): EmbeddingInterfaceCompat? {
+        private fun initAndVerifyEmbeddingExtension(
+            applicationContext: Context
+        ): EmbeddingInterfaceCompat? {
             var impl: EmbeddingInterfaceCompat? = null
             try {
                 if (isExtensionVersionSupported(ExtensionsUtil.safeVendorApiLevel) &&
@@ -79,7 +82,8 @@
                         EmbeddingCompat(
                             EmbeddingCompat.embeddingComponent(),
                             EmbeddingAdapter(PredicateAdapter(loader)),
-                            ConsumerAdapter(loader)
+                            ConsumerAdapter(loader),
+                            applicationContext
                         )
                     }
                     // TODO(b/190433400): Check API conformance
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitController.kt b/window/window/src/main/java/androidx/window/embedding/SplitController.kt
index 0c6555e..d24c68a 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitController.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitController.kt
@@ -36,8 +36,9 @@
  * [android.app.Activity] launches and do not apply to already running activities.
  * @see initialize
  */
-class SplitController private constructor() {
-    private val embeddingBackend: EmbeddingBackend = ExtensionEmbeddingBackend.getInstance()
+class SplitController private constructor(applicationContext: Context) {
+    private val embeddingBackend: EmbeddingBackend = ExtensionEmbeddingBackend
+        .getInstance(applicationContext)
     private var staticSplitRules: Set<EmbeddingRule> = emptySet()
 
     /**
@@ -151,13 +152,15 @@
 
         /**
          * Gets the shared instance of the class.
+         *
+         * @param context the [Context] to initialize the controller with.
          */
         @JvmStatic
-        fun getInstance(): SplitController {
+        fun getInstance(context: Context): SplitController {
             if (globalInstance == null) {
                 globalLock.withLock {
                     if (globalInstance == null) {
-                        globalInstance = SplitController()
+                        globalInstance = SplitController(context.applicationContext)
                     }
                 }
             }
@@ -175,7 +178,8 @@
          * If the app doesn't have any static rule, it can use [registerRule] to register rules at
          * any time.
          *
-         * @param context the context to read the split resource from.
+         * @param context the [Context] to read the split resource from, and to initialize the
+         * controller with.
          * @param staticRuleResourceId the resource containing the static split rules.
          * @throws IllegalArgumentException if any of the rules in the XML is malformed or if
          * there's a duplicated [tag][EmbeddingRule.tag].
@@ -184,7 +188,7 @@
         fun initialize(context: Context, staticRuleResourceId: Int) {
             val parser = SplitRuleParser()
             val configs = parser.parseSplitRules(context, staticRuleResourceId)
-            val controllerInstance = getInstance()
+            val controllerInstance = getInstance(context)
             controllerInstance.setStaticSplitRules(configs ?: emptySet())
         }
     }
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
index e6c083b..cf64932 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
@@ -66,13 +66,13 @@
         @SplitFinishBehavior finishPrimaryWithSecondary: Int = FINISH_NEVER,
         @SplitFinishBehavior finishSecondaryWithPrimary: Int = FINISH_ALWAYS,
         clearTop: Boolean = false,
-        @IntRange(from = 0) minWidth: Int,
-        @IntRange(from = 0) minSmallestWidth: Int,
+        @IntRange(from = 0) minWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP,
+        @IntRange(from = 0) minSmallestWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP,
         @FloatRange(from = 0.0, to = 1.0) splitRatio: Float = 0.5f,
         @LayoutDirection layoutDirection: Int = LOCALE
-    ) : super(minWidth, minSmallestWidth, splitRatio, layoutDirection) {
-        checkArgumentNonnegative(minWidth, "minWidth must be non-negative")
-        checkArgumentNonnegative(minSmallestWidth, "minSmallestWidth must be non-negative")
+    ) : super(minWidthDp, minSmallestWidthDp, splitRatio, layoutDirection) {
+        checkArgumentNonnegative(minWidthDp, "minWidthDp must be non-negative")
+        checkArgumentNonnegative(minSmallestWidthDp, "minSmallestWidthDp must be non-negative")
         checkArgument(splitRatio in 0.0..1.0, "splitRatio must be in 0.0..1.0 range")
         this.filters = filters.toSet()
         this.clearTop = clearTop
@@ -82,17 +82,16 @@
 
     /**
      * Builder for [SplitPairRule].
+     *
      * @param filters See [SplitPairRule.filters].
-     * @param minWidth See [SplitPairRule.minWidth].
-     * @param minSmallestWidth See [SplitPairRule.minSmallestWidth].
      */
     class Builder(
         private val filters: Set<SplitPairFilter>,
-        @IntRange(from = 0)
-        private val minWidth: Int,
-        @IntRange(from = 0)
-        private val minSmallestWidth: Int
     ) {
+        @IntRange(from = 0)
+        private var minWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP
+        @IntRange(from = 0)
+        private var minSmallestWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP
         @SplitFinishBehavior
         private var finishPrimaryWithSecondary: Int = FINISH_NEVER
         @SplitFinishBehavior
@@ -104,6 +103,18 @@
         private var layoutDirection: Int = LOCALE
 
         /**
+         * @see SplitPairRule.minWidthDp
+         */
+        fun setMinWidthDp(@IntRange(from = 0) minWidthDp: Int): Builder =
+            apply { this.minWidthDp = minWidthDp }
+
+        /**
+         * @see SplitPairRule.minSmallestWidthDp
+         */
+        fun setMinSmallestWidthDp(@IntRange(from = 0) minSmallestWidthDp: Int): Builder =
+            apply { this.minSmallestWidthDp = minSmallestWidthDp }
+
+        /**
          * @see SplitPairRule.finishPrimaryWithSecondary
          */
         fun setFinishPrimaryWithSecondary(
@@ -139,7 +150,7 @@
             apply { this.layoutDirection = layoutDirection }
 
         fun build() = SplitPairRule(filters, finishPrimaryWithSecondary, finishSecondaryWithPrimary,
-            clearTop, minWidth, minSmallestWidth, splitRatio, layoutDirection)
+            clearTop, minWidthDp, minSmallestWidthDp, splitRatio, layoutDirection)
     }
 
     /**
@@ -150,7 +161,9 @@
         val newSet = mutableSetOf<SplitPairFilter>()
         newSet.addAll(filters)
         newSet.add(filter)
-        return Builder(newSet.toSet(), minWidth, minSmallestWidth)
+        return Builder(newSet.toSet())
+            .setMinWidthDp(minWidthDp)
+            .setMinSmallestWidthDp(minSmallestWidthDp)
             .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
             .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
             .setClearTop(clearTop)
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
index 3d10737..6c03c7b 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
@@ -77,13 +77,13 @@
         placeholderIntent: Intent,
         isSticky: Boolean,
         @SplitPlaceholderFinishBehavior finishPrimaryWithPlaceholder: Int = FINISH_ALWAYS,
-        @IntRange(from = 0) minWidth: Int = 0,
-        @IntRange(from = 0) minSmallestWidth: Int = 0,
+        @IntRange(from = 0) minWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP,
+        @IntRange(from = 0) minSmallestWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP,
         @FloatRange(from = 0.0, to = 1.0) splitRatio: Float = 0.5f,
         @LayoutDirection layoutDirection: Int = LOCALE
-    ) : super(minWidth, minSmallestWidth, splitRatio, layoutDirection) {
-        checkArgumentNonnegative(minWidth, "minWidth must be non-negative")
-        checkArgumentNonnegative(minSmallestWidth, "minSmallestWidth must be non-negative")
+    ) : super(minWidthDp, minSmallestWidthDp, splitRatio, layoutDirection) {
+        checkArgumentNonnegative(minWidthDp, "minWidthDp must be non-negative")
+        checkArgumentNonnegative(minSmallestWidthDp, "minSmallestWidthDp must be non-negative")
         checkArgument(splitRatio in 0.0..1.0, "splitRatio must be in 0.0..1.0 range")
         checkArgument(finishPrimaryWithPlaceholder != FINISH_NEVER,
             "FINISH_NEVER is not a valid configuration for SplitPlaceholderRule. " +
@@ -96,19 +96,18 @@
 
     /**
      * Builder for [SplitPlaceholderRule].
+     *
      * @param filters See [SplitPlaceholderRule.filters].
      * @param placeholderIntent See [SplitPlaceholderRule.placeholderIntent].
-     * @param minWidth See [SplitPlaceholderRule.minWidth].
-     * @param minSmallestWidth See [SplitPlaceholderRule.minSmallestWidth].
      */
     class Builder(
         private val filters: Set<ActivityFilter>,
         private val placeholderIntent: Intent,
-        @IntRange(from = 0)
-        private val minWidth: Int,
-        @IntRange(from = 0)
-        private val minSmallestWidth: Int
     ) {
+        @IntRange(from = 0)
+        private var minWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP
+        @IntRange(from = 0)
+        private var minSmallestWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP
         @SplitPlaceholderFinishBehavior
         private var finishPrimaryWithPlaceholder: Int = FINISH_ALWAYS
         private var isSticky: Boolean = false
@@ -118,6 +117,18 @@
         private var layoutDirection: Int = LOCALE
 
         /**
+         * @see SplitPlaceholderRule.minWidthDp
+         */
+        fun setMinWidthDp(@IntRange(from = 0) minWidthDp: Int): Builder =
+            apply { this.minWidthDp = minWidthDp }
+
+        /**
+         * @see SplitPlaceholderRule.minSmallestWidthDp
+         */
+        fun setMinSmallestWidthDp(@IntRange(from = 0) minSmallestWidthDp: Int): Builder =
+            apply { this.minSmallestWidthDp = minSmallestWidthDp }
+
+        /**
          * @see SplitPlaceholderRule.finishPrimaryWithPlaceholder
          */
         fun setFinishPrimaryWithPlaceholder(
@@ -146,7 +157,8 @@
             apply { this.layoutDirection = layoutDirection }
 
         fun build() = SplitPlaceholderRule(filters, placeholderIntent, isSticky,
-            finishPrimaryWithPlaceholder, minWidth, minSmallestWidth, splitRatio, layoutDirection)
+            finishPrimaryWithPlaceholder, minWidthDp, minSmallestWidthDp, splitRatio,
+            layoutDirection)
     }
 
     /**
@@ -157,7 +169,9 @@
         val newSet = mutableSetOf<ActivityFilter>()
         newSet.addAll(filters)
         newSet.add(filter)
-        return Builder(newSet.toSet(), placeholderIntent, minWidth, minSmallestWidth)
+        return Builder(newSet.toSet(), placeholderIntent)
+            .setMinWidthDp(minWidthDp)
+            .setMinSmallestWidthDp(minSmallestWidthDp)
             .setSticky(isSticky)
             .setFinishPrimaryWithPlaceholder(finishPrimaryWithPlaceholder)
             .setSplitRatio(splitRatio)
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
index d588888..92c210f 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
@@ -16,6 +16,7 @@
 
 package androidx.window.embedding
 
+import android.content.Context
 import android.graphics.Rect
 import android.os.Build
 import android.util.LayoutDirection.LOCALE
@@ -27,6 +28,7 @@
 import androidx.annotation.IntDef
 import androidx.annotation.IntRange
 import androidx.annotation.RequiresApi
+import androidx.window.embedding.SplitRule.Companion.DEFAULT_SPLIT_MIN_DIMENSION_DP
 import kotlin.math.min
 
 /**
@@ -39,21 +41,27 @@
  */
 open class SplitRule internal constructor(
     /**
-     * The smallest value of width of the parent window when the split should be used, in pixels.
+     * The smallest value of width of the parent window when the split should be used, in DP.
      * When the window size is smaller than requested here, activities in the secondary container
      * will be stacked on top of the activities in the primary one, completely overlapping them.
+     *
+     * The default is [DEFAULT_SPLIT_MIN_DIMENSION_DP] if the app doesn't set.
+     * `0` means to always allow split.
      */
     @IntRange(from = 0)
-    val minWidth: Int,
+    val minWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP,
 
     /**
      * The smallest value of the smallest possible width of the parent window in any rotation
-     * when the split should be used, in pixels. When the window size is smaller than requested
+     * when the split should be used, in DP. When the window size is smaller than requested
      * here, activities in the secondary container will be stacked on top of the activities in
      * the primary one, completely overlapping them.
+     *
+     * The default is [DEFAULT_SPLIT_MIN_DIMENSION_DP] if the app doesn't set.
+     * `0` means to always allow split.
      */
     @IntRange(from = 0)
-    val minSmallestWidth: Int,
+    val minSmallestWidthDp: Int,
 
     /**
      * Defines what part of the width should be given to the primary activity. Defaults to an
@@ -104,6 +112,11 @@
          * @see SplitRule.Companion
          */
         const val FINISH_ADJACENT = 2
+        /**
+         * The default min dimension in DP for allowing split if it is not set by apps. The value
+         * reflects [androidx.window.core.layout.WindowWidthSizeClass.MEDIUM].
+         */
+        const val DEFAULT_SPLIT_MIN_DIMENSION_DP = 600
     }
 
     /**
@@ -117,26 +130,38 @@
     /**
      * Verifies if the provided parent bounds are large enough to apply the rule.
      */
-    internal fun checkParentMetrics(parentMetrics: WindowMetrics): Boolean {
+    internal fun checkParentMetrics(context: Context, parentMetrics: WindowMetrics): Boolean {
         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
             return false
         }
         val bounds = Api30Impl.getBounds(parentMetrics)
-        return checkParentBounds(bounds)
+        // TODO(b/257000820): Application displayMetrics should only be used as a fallback. Replace
+        // with Task density after we include it in WindowMetrics.
+        val density = context.resources.displayMetrics.density
+        return checkParentBounds(density, bounds)
     }
 
     /**
      * @see checkParentMetrics
      */
-    internal fun checkParentBounds(bounds: Rect): Boolean {
-        val validMinWidth = (minWidth == 0 || bounds.width() >= minWidth)
+    internal fun checkParentBounds(density: Float, bounds: Rect): Boolean {
+        val minWidthPx = convertDpToPx(density, minWidthDp)
+        val minSmallestWidthPx = convertDpToPx(density, minSmallestWidthDp)
+        val validMinWidth = (minWidthDp == 0 || bounds.width() >= minWidthPx)
         val validSmallestMinWidth = (
-            minSmallestWidth == 0 ||
-                min(bounds.width(), bounds.height()) >= minSmallestWidth
+            minSmallestWidthDp == 0 ||
+                min(bounds.width(), bounds.height()) >= minSmallestWidthPx
             )
         return validMinWidth && validSmallestMinWidth
     }
 
+    /**
+     * Converts the dimension from Dp to pixels.
+     */
+    private fun convertDpToPx(density: Float, @IntRange(from = 0) dimensionDp: Int): Int {
+        return (dimensionDp * density + 0.5f).toInt()
+    }
+
     @RequiresApi(30)
     internal object Api30Impl {
         @DoNotInline
@@ -149,8 +174,8 @@
         if (this === other) return true
         if (other !is SplitRule) return false
 
-        if (minWidth != other.minWidth) return false
-        if (minSmallestWidth != other.minSmallestWidth) return false
+        if (minWidthDp != other.minWidthDp) return false
+        if (minSmallestWidthDp != other.minSmallestWidthDp) return false
         if (splitRatio != other.splitRatio) return false
         if (layoutDirection != other.layoutDirection) return false
 
@@ -158,8 +183,8 @@
     }
 
     override fun hashCode(): Int {
-        var result = minWidth
-        result = 31 * result + minSmallestWidth
+        var result = minWidthDp
+        result = 31 * result + minSmallestWidthDp
         result = 31 * result + splitRatio.hashCode()
         result = 31 * result + layoutDirection
         return result
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitRuleParser.kt b/window/window/src/main/java/androidx/window/embedding/SplitRuleParser.kt
index 7e54dc0..10afe8b 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitRuleParser.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitRuleParser.kt
@@ -124,8 +124,8 @@
         parser: XmlResourceParser
     ): SplitPairRule {
         val ratio: Float
-        val minWidth: Int
-        val minSmallestWidth: Int
+        val minWidthDp: Int
+        val minSmallestWidthDp: Int
         val layoutDir: Int
         val finishPrimaryWithSecondary: Int
         val finishSecondaryWithPrimary: Int
@@ -137,14 +137,14 @@
             0
         ).apply {
             ratio = getFloat(R.styleable.SplitPairRule_splitRatio, 0.5f)
-            minWidth = getDimension(
-                R.styleable.SplitPairRule_splitMinWidth,
-                defaultMinWidth(context.resources)
-            ).toInt()
-            minSmallestWidth = getDimension(
-                R.styleable.SplitPairRule_splitMinSmallestWidth,
-                defaultMinWidth(context.resources)
-            ).toInt()
+            minWidthDp = getInteger(
+                R.styleable.SplitPairRule_splitMinWidthDp,
+                SplitRule.DEFAULT_SPLIT_MIN_DIMENSION_DP
+            )
+            minSmallestWidthDp = getInteger(
+                R.styleable.SplitPairRule_splitMinSmallestWidthDp,
+                SplitRule.DEFAULT_SPLIT_MIN_DIMENSION_DP
+            )
             layoutDir = getInt(
                 R.styleable.SplitPairRule_splitLayoutDirection,
                 LayoutDirection.LOCALE
@@ -156,7 +156,9 @@
             clearTop =
                 getBoolean(R.styleable.SplitPairRule_clearTop, false)
         }
-        return SplitPairRule.Builder(emptySet(), minWidth, minSmallestWidth)
+        return SplitPairRule.Builder(emptySet())
+            .setMinWidthDp(minWidthDp)
+            .setMinSmallestWidthDp(minSmallestWidthDp)
             .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
             .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
             .setClearTop(clearTop)
@@ -173,8 +175,8 @@
         val stickyPlaceholder: Boolean
         val finishPrimaryWithPlaceholder: Int
         val ratio: Float
-        val minWidth: Int
-        val minSmallestWidth: Int
+        val minWidthDp: Int
+        val minSmallestWidthDp: Int
         val layoutDir: Int
         context.theme.obtainStyledAttributes(
             parser,
@@ -190,14 +192,14 @@
             finishPrimaryWithPlaceholder =
                 getInt(R.styleable.SplitPlaceholderRule_finishPrimaryWithPlaceholder, FINISH_ALWAYS)
             ratio = getFloat(R.styleable.SplitPlaceholderRule_splitRatio, 0.5f)
-            minWidth = getDimension(
-                R.styleable.SplitPlaceholderRule_splitMinWidth,
-                defaultMinWidth(context.resources)
-            ).toInt()
-            minSmallestWidth = getDimension(
-                R.styleable.SplitPlaceholderRule_splitMinSmallestWidth,
-                defaultMinWidth(context.resources)
-            ).toInt()
+            minWidthDp = getInteger(
+                R.styleable.SplitPlaceholderRule_splitMinWidthDp,
+                SplitRule.DEFAULT_SPLIT_MIN_DIMENSION_DP
+            )
+            minSmallestWidthDp = getInteger(
+                R.styleable.SplitPlaceholderRule_splitMinSmallestWidthDp,
+                SplitRule.DEFAULT_SPLIT_MIN_DIMENSION_DP
+            )
             layoutDir = getInt(
                 R.styleable.SplitPlaceholderRule_splitLayoutDirection,
                 LayoutDirection.LOCALE
@@ -217,10 +219,11 @@
 
         return SplitPlaceholderRule.Builder(
             emptySet(),
-            Intent().setComponent(placeholderActivityClassName),
-            minWidth,
-            minSmallestWidth
-        ).setSticky(stickyPlaceholder)
+            Intent().setComponent(placeholderActivityClassName)
+        )
+            .setMinWidthDp(minWidthDp)
+            .setMinSmallestWidthDp(minSmallestWidthDp)
+            .setSticky(stickyPlaceholder)
             .setFinishPrimaryWithPlaceholder(finishPrimaryWithPlaceholder)
             .setSplitRatio(ratio)
             .setLayoutDirection(layoutDir)
@@ -320,11 +323,4 @@
         }
         return ComponentName(pkgString, clsString)
     }
-
-    private fun defaultMinWidth(resources: Resources): Float {
-        // Get the screen's density scale
-        val scale: Float = resources.displayMetrics.density
-        // Convert the dps to pixels, based on density scale
-        return 600 * scale + 0.5f
-    }
 }
diff --git a/window/window/src/main/res/values/attrs.xml b/window/window/src/main/res/values/attrs.xml
index c7f284db..35b0d8f 100644
--- a/window/window/src/main/res/values/attrs.xml
+++ b/window/window/src/main/res/values/attrs.xml
@@ -19,10 +19,10 @@
     equal width split. -->
     <attr name="splitRatio" format="float" />
     <!-- The smallest value of width of the parent window when the split should be used. -->
-    <attr name="splitMinWidth" format="dimension" />
+    <attr name="splitMinWidthDp" format="integer" />
     <!-- The smallest value of the smallest-width (sw) of the parent window in any rotation when
      the split should be used. -->
-    <attr name="splitMinSmallestWidth" format="dimension" />
+    <attr name="splitMinSmallestWidthDp" format="integer" />
     <attr name="splitLayoutDirection" format="enum">
         <enum name="locale" value="0" />
         <enum name="ltr" value="1" />
@@ -58,8 +58,8 @@
          to "false". -->
         <attr name="clearTop" format="boolean" />
         <attr name="splitRatio"/>
-        <attr name="splitMinWidth"/>
-        <attr name="splitMinSmallestWidth"/>
+        <attr name="splitMinWidthDp"/>
+        <attr name="splitMinSmallestWidthDp"/>
         <attr name="splitLayoutDirection"/>
     </declare-styleable>
 
@@ -76,8 +76,8 @@
          primary container that created the split should also be finished. Defaults to "always". -->
         <attr name="finishPrimaryWithPlaceholder" />
         <attr name="splitRatio"/>
-        <attr name="splitMinWidth"/>
-        <attr name="splitMinSmallestWidth"/>
+        <attr name="splitMinWidthDp"/>
+        <attr name="splitMinSmallestWidthDp"/>
         <attr name="splitLayoutDirection"/>
     </declare-styleable>
 
diff --git a/window/window/src/test/java/androidx/window/embedding/EmbeddingCompatTest.kt b/window/window/src/test/java/androidx/window/embedding/EmbeddingCompatTest.kt
index b1f6a6b..d5db2ca 100644
--- a/window/window/src/test/java/androidx/window/embedding/EmbeddingCompatTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/EmbeddingCompatTest.kt
@@ -30,7 +30,8 @@
 class EmbeddingCompatTest {
 
     private val component = mock<ActivityEmbeddingComponent>()
-    private val embeddingCompat = EmbeddingCompat(component, EMBEDDING_ADAPTER, CONSUMER_ADAPTER)
+    private val embeddingCompat = EmbeddingCompat(component, EMBEDDING_ADAPTER, CONSUMER_ADAPTER,
+        mock())
 
     @Test
     fun setSplitInfoCallback_callsActualMethod() {
diff --git a/work/work-runtime/api/current.ignore b/work/work-runtime/api/current.ignore
new file mode 100644
index 0000000..33b7b2f
--- /dev/null
+++ b/work/work-runtime/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+ChangedType: androidx.work.WorkInfo#getTags():
+    Method androidx.work.WorkInfo.getTags has changed return type from java.util.Set<java.lang.String!> to java.util.Set<java.lang.String>
diff --git a/work/work-runtime/api/current.txt b/work/work-runtime/api/current.txt
index 446ee00..370e634 100644
--- a/work/work-runtime/api/current.txt
+++ b/work/work-runtime/api/current.txt
@@ -320,13 +320,23 @@
     method public java.util.UUID getId();
     method public androidx.work.Data getOutputData();
     method public androidx.work.Data getProgress();
-    method @IntRange(from=0) public int getRunAttemptCount();
+    method @IntRange(from=0L) public int getRunAttemptCount();
     method public androidx.work.WorkInfo.State getState();
-    method public java.util.Set<java.lang.String!> getTags();
+    method public java.util.Set<java.lang.String> getTags();
+    property public final int generation;
+    property public final java.util.UUID id;
+    property public final androidx.work.Data outputData;
+    property public final androidx.work.Data progress;
+    property @IntRange(from=0L) public final int runAttemptCount;
+    property public final androidx.work.WorkInfo.State state;
+    property public final java.util.Set<java.lang.String> tags;
   }
 
   public enum WorkInfo.State {
-    method public boolean isFinished();
+    method public final boolean isFinished();
+    method public static androidx.work.WorkInfo.State valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.work.WorkInfo.State[] values();
+    property public final boolean isFinished;
     enum_constant public static final androidx.work.WorkInfo.State BLOCKED;
     enum_constant public static final androidx.work.WorkInfo.State CANCELLED;
     enum_constant public static final androidx.work.WorkInfo.State ENQUEUED;
diff --git a/work/work-runtime/api/public_plus_experimental_current.txt b/work/work-runtime/api/public_plus_experimental_current.txt
index 446ee00..370e634 100644
--- a/work/work-runtime/api/public_plus_experimental_current.txt
+++ b/work/work-runtime/api/public_plus_experimental_current.txt
@@ -320,13 +320,23 @@
     method public java.util.UUID getId();
     method public androidx.work.Data getOutputData();
     method public androidx.work.Data getProgress();
-    method @IntRange(from=0) public int getRunAttemptCount();
+    method @IntRange(from=0L) public int getRunAttemptCount();
     method public androidx.work.WorkInfo.State getState();
-    method public java.util.Set<java.lang.String!> getTags();
+    method public java.util.Set<java.lang.String> getTags();
+    property public final int generation;
+    property public final java.util.UUID id;
+    property public final androidx.work.Data outputData;
+    property public final androidx.work.Data progress;
+    property @IntRange(from=0L) public final int runAttemptCount;
+    property public final androidx.work.WorkInfo.State state;
+    property public final java.util.Set<java.lang.String> tags;
   }
 
   public enum WorkInfo.State {
-    method public boolean isFinished();
+    method public final boolean isFinished();
+    method public static androidx.work.WorkInfo.State valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.work.WorkInfo.State[] values();
+    property public final boolean isFinished;
     enum_constant public static final androidx.work.WorkInfo.State BLOCKED;
     enum_constant public static final androidx.work.WorkInfo.State CANCELLED;
     enum_constant public static final androidx.work.WorkInfo.State ENQUEUED;
diff --git a/work/work-runtime/api/restricted_current.ignore b/work/work-runtime/api/restricted_current.ignore
new file mode 100644
index 0000000..33b7b2f
--- /dev/null
+++ b/work/work-runtime/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+ChangedType: androidx.work.WorkInfo#getTags():
+    Method androidx.work.WorkInfo.getTags has changed return type from java.util.Set<java.lang.String!> to java.util.Set<java.lang.String>
diff --git a/work/work-runtime/api/restricted_current.txt b/work/work-runtime/api/restricted_current.txt
index 446ee00..370e634 100644
--- a/work/work-runtime/api/restricted_current.txt
+++ b/work/work-runtime/api/restricted_current.txt
@@ -320,13 +320,23 @@
     method public java.util.UUID getId();
     method public androidx.work.Data getOutputData();
     method public androidx.work.Data getProgress();
-    method @IntRange(from=0) public int getRunAttemptCount();
+    method @IntRange(from=0L) public int getRunAttemptCount();
     method public androidx.work.WorkInfo.State getState();
-    method public java.util.Set<java.lang.String!> getTags();
+    method public java.util.Set<java.lang.String> getTags();
+    property public final int generation;
+    property public final java.util.UUID id;
+    property public final androidx.work.Data outputData;
+    property public final androidx.work.Data progress;
+    property @IntRange(from=0L) public final int runAttemptCount;
+    property public final androidx.work.WorkInfo.State state;
+    property public final java.util.Set<java.lang.String> tags;
   }
 
   public enum WorkInfo.State {
-    method public boolean isFinished();
+    method public final boolean isFinished();
+    method public static androidx.work.WorkInfo.State valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.work.WorkInfo.State[] values();
+    property public final boolean isFinished;
     enum_constant public static final androidx.work.WorkInfo.State BLOCKED;
     enum_constant public static final androidx.work.WorkInfo.State CANCELLED;
     enum_constant public static final androidx.work.WorkInfo.State ENQUEUED;
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkInfo.java b/work/work-runtime/src/main/java/androidx/work/WorkInfo.java
deleted file mode 100644
index 7560af3..0000000
--- a/work/work-runtime/src/main/java/androidx/work/WorkInfo.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright 2018 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.work;
-
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
-
-/**
- * Information about a particular {@link WorkRequest} containing the id of the WorkRequest, its
- * current {@link State}, output, tags, and run attempt count.  Note that output is only available
- * for the terminal states ({@link State#SUCCEEDED} and {@link State#FAILED}).
- */
-
-public final class WorkInfo {
-
-    private @NonNull UUID mId;
-    private @NonNull State mState;
-    private @NonNull Data mOutputData;
-    private @NonNull Set<String> mTags;
-    private @NonNull Data mProgress;
-    private int mRunAttemptCount;
-
-    private final int mGeneration;
-    /**
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public WorkInfo(
-            @NonNull UUID id,
-            @NonNull State state,
-            @NonNull Data outputData,
-            @NonNull List<String> tags,
-            @NonNull Data progress,
-            int runAttemptCount,
-            int generation) {
-        mId = id;
-        mState = state;
-        mOutputData = outputData;
-        mTags = new HashSet<>(tags);
-        mProgress = progress;
-        mRunAttemptCount = runAttemptCount;
-        mGeneration = generation;
-    }
-
-    /**
-     * Gets the identifier of the {@link WorkRequest}.
-     *
-     * @return The identifier of a {@link WorkRequest}
-     */
-    public @NonNull UUID getId() {
-        return mId;
-    }
-
-    /**
-     * Gets the current {@link State} of the {@link WorkRequest}.
-     *
-     * @return The current {@link State} of the {@link WorkRequest}
-     */
-    public @NonNull State getState() {
-        return mState;
-    }
-
-    /**
-     * Gets the output {@link Data} for the {@link WorkRequest}.  If the WorkRequest is unfinished,
-     * this is always {@link Data#EMPTY}.
-     *
-     * @return The output {@link Data} of the {@link WorkRequest}
-     */
-    public @NonNull Data getOutputData() {
-        return mOutputData;
-    }
-
-    /**
-     * Gets the {@link Set} of tags associated with the {@link WorkRequest}.
-     *
-     * @return The {@link Set} of tags associated with the {@link WorkRequest}
-     */
-    public @NonNull Set<String> getTags() {
-        return mTags;
-    }
-
-    /**
-     * Gets the progress {@link Data} associated with the {@link WorkRequest}.
-     *
-     * @return The progress {@link Data} associated with the {@link WorkRequest}
-     */
-    public @NonNull Data getProgress() {
-        return mProgress;
-    }
-
-    /**
-     * Gets the run attempt count of the {@link WorkRequest}.  Note that for
-     * {@link PeriodicWorkRequest}s, the run attempt count gets reset between successful runs.
-     *
-     * @return The run attempt count of the {@link WorkRequest}.
-     */
-    @IntRange(from = 0)
-    public int getRunAttemptCount() {
-        return mRunAttemptCount;
-    }
-
-    /**
-     * Gets the latest generation of this Worker.
-     * <p>
-     * A work has multiple generations, if it was updated via
-     * {@link WorkManager#updateWork(WorkRequest)} or
-     * {@link WorkManager#enqueueUniquePeriodicWork(String,
-     * ExistingPeriodicWorkPolicy, PeriodicWorkRequest)} using
-     * {@link ExistingPeriodicWorkPolicy#UPDATE}.
-     * <p>
-     * If this worker is currently running, it can possibly be of an older generation rather than
-     * returned by this function if an update has happened during an execution of this worker.
-     *
-     * @return a generation of this work.
-     */
-    public int getGeneration() {
-        return mGeneration;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        WorkInfo workInfo = (WorkInfo) o;
-
-        if (mRunAttemptCount != workInfo.mRunAttemptCount) return false;
-        if (mGeneration != workInfo.mGeneration) return false;
-        if (!mId.equals(workInfo.mId)) return false;
-        if (mState != workInfo.mState) return false;
-        if (!mOutputData.equals(workInfo.mOutputData)) return false;
-        if (!mTags.equals(workInfo.mTags)) return false;
-        return mProgress.equals(workInfo.mProgress);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mId.hashCode();
-        result = 31 * result + mState.hashCode();
-        result = 31 * result + mOutputData.hashCode();
-        result = 31 * result + mTags.hashCode();
-        result = 31 * result + mProgress.hashCode();
-        result = 31 * result + mRunAttemptCount;
-        result = 31 * result + mGeneration;
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "WorkInfo{"
-                +   "mId='" + mId + '\''
-                +   ", mState=" + mState
-                +   ", mOutputData=" + mOutputData
-                +   ", mTags=" + mTags
-                +   ", mProgress=" + mProgress
-                + '}';
-    }
-
-    /**
-     * The current lifecycle state of a {@link WorkRequest}.
-     */
-    public enum State {
-
-        /**
-         * Used to indicate that the {@link WorkRequest} is enqueued and eligible to run when its
-         * {@link Constraints} are met and resources are available.
-         */
-        ENQUEUED,
-
-        /**
-         * Used to indicate that the {@link WorkRequest} is currently being executed.
-         */
-        RUNNING,
-
-        /**
-         * Used to indicate that the {@link WorkRequest} has completed in a successful state.  Note
-         * that {@link PeriodicWorkRequest}s will never enter this state (they will simply go back
-         * to {@link #ENQUEUED} and be eligible to run again).
-         */
-        SUCCEEDED,
-
-        /**
-         * Used to indicate that the {@link WorkRequest} has completed in a failure state.  All
-         * dependent work will also be marked as {@code #FAILED} and will never run.
-         */
-        FAILED,
-
-        /**
-         * Used to indicate that the {@link WorkRequest} is currently blocked because its
-         * prerequisites haven't finished successfully.
-         */
-        BLOCKED,
-
-        /**
-         * Used to indicate that the {@link WorkRequest} has been cancelled and will not execute.
-         * All dependent work will also be marked as {@code #CANCELLED} and will not run.
-         */
-        CANCELLED;
-
-        /**
-         * Returns {@code true} if this State is considered finished.
-         *
-         * @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and * {@link #CANCELLED}
-         *         states
-         */
-        public boolean isFinished() {
-            return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
-        }
-    }
-}
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkInfo.kt b/work/work-runtime/src/main/java/androidx/work/WorkInfo.kt
new file mode 100644
index 0000000..5d4ab92
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/WorkInfo.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2018 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.work
+
+import androidx.annotation.IntRange
+import androidx.annotation.RestrictTo
+import androidx.work.WorkInfo.State
+import java.util.UUID
+
+/**
+ * Information about a particular [WorkRequest] containing the id of the WorkRequest, its
+ * current [State], output, tags, and run attempt count.  Note that output is only available
+ * for the terminal states ([State.SUCCEEDED] and [State.FAILED]).
+ */
+class WorkInfo @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) constructor(
+    /**
+     * The identifier of the [WorkRequest].
+     */
+    val id: UUID,
+    /**
+     * The current [State] of the [WorkRequest].
+     */
+    val state: State,
+    /**
+     * The output [Data] for the [WorkRequest]. If the WorkRequest is unfinished,
+     * this is always [Data.EMPTY].
+     */
+    val outputData: Data,
+
+    tags: List<String>,
+    /**
+     * The progress [Data] associated with the [WorkRequest].
+     */
+    val progress: Data,
+
+    /**
+     * The run attempt count of the [WorkRequest].  Note that for
+     * [PeriodicWorkRequest]s, the run attempt count gets reset between successful runs.
+     */
+    @get:IntRange(from = 0)
+    val runAttemptCount: Int,
+
+    /**
+     * The latest generation of this Worker.
+     *
+     *
+     * A work has multiple generations, if it was updated via
+     * [WorkManager.updateWork] or
+     * [WorkManager.enqueueUniquePeriodicWork] using
+     * [ExistingPeriodicWorkPolicy.UPDATE].
+     *
+     *
+     * If this worker is currently running, it can possibly be of an older generation rather than
+     * returned by this function if an update has happened during an execution of this worker.
+     */
+    val generation: Int
+) {
+    /**
+     * The [Set] of tags associated with the [WorkRequest].
+     */
+    val tags: Set<String> = HashSet(tags)
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || javaClass != other.javaClass) return false
+        val workInfo = other as WorkInfo
+        if (runAttemptCount != workInfo.runAttemptCount) return false
+        if (generation != workInfo.generation) return false
+        if (id != workInfo.id) return false
+        if (state != workInfo.state) return false
+        if (outputData != workInfo.outputData) return false
+        return if (tags != workInfo.tags) false else progress == workInfo.progress
+    }
+
+    override fun hashCode(): Int {
+        var result = id.hashCode()
+        result = 31 * result + state.hashCode()
+        result = 31 * result + outputData.hashCode()
+        result = 31 * result + tags.hashCode()
+        result = 31 * result + progress.hashCode()
+        result = 31 * result + runAttemptCount
+        result = 31 * result + generation
+        return result
+    }
+
+    override fun toString(): String {
+        return ("WorkInfo{mId='$id', mState=$state, " +
+            "mOutputData=$outputData, mTags=$tags, mProgress=$progress}")
+    }
+
+    /**
+     * The current lifecycle state of a [WorkRequest].
+     */
+    enum class State {
+        /**
+         * Used to indicate that the [WorkRequest] is enqueued and eligible to run when its
+         * [Constraints] are met and resources are available.
+         */
+        ENQUEUED,
+
+        /**
+         * Used to indicate that the [WorkRequest] is currently being executed.
+         */
+        RUNNING,
+
+        /**
+         * Used to indicate that the [WorkRequest] has completed in a successful state.  Note
+         * that [PeriodicWorkRequest]s will never enter this state (they will simply go back
+         * to [.ENQUEUED] and be eligible to run again).
+         */
+        SUCCEEDED,
+
+        /**
+         * Used to indicate that the [WorkRequest] has completed in a failure state.  All
+         * dependent work will also be marked as `#FAILED` and will never run.
+         */
+        FAILED,
+
+        /**
+         * Used to indicate that the [WorkRequest] is currently blocked because its
+         * prerequisites haven't finished successfully.
+         */
+        BLOCKED,
+
+        /**
+         * Used to indicate that the [WorkRequest] has been cancelled and will not execute.
+         * All dependent work will also be marked as `#CANCELLED` and will not run.
+         */
+        CANCELLED;
+
+        /**
+         * Returns `true` if this State is considered finished:
+         * [.SUCCEEDED], [.FAILED], and * [.CANCELLED]
+         */
+        val isFinished: Boolean
+            get() = this == SUCCEEDED || this == FAILED || this == CANCELLED
+    }
+}
\ No newline at end of file