Merge "Bump collections to 1.4.0-beta02" into androidx-main
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 67c188e..37c352c 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -24,6 +24,8 @@
 
   @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.DocumentProperty {
     method public abstract boolean indexNestedProperties() default false;
+    method public abstract String[] indexableNestedPropertiesList() default {};
+    method public abstract boolean inheritIndexableNestedPropertiesFromSuperclass() default false;
     method public abstract String name() default "";
     method public abstract boolean required() default false;
   }
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 67c188e..37c352c 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -24,6 +24,8 @@
 
   @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.DocumentProperty {
     method public abstract boolean indexNestedProperties() default false;
+    method public abstract String[] indexableNestedPropertiesList() default {};
+    method public abstract boolean inheritIndexableNestedPropertiesFromSuperclass() default false;
     method public abstract String name() default "";
     method public abstract boolean required() default false;
   }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
index 77db243..5d52268 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
@@ -16,6 +16,8 @@
 // @exportToFramework:skipFile()
 package androidx.appsearch.app;
 
+import static androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE;
+import static androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS;
 import static androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES;
 import static androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_QUALIFIED_ID;
 import static androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN;
@@ -875,6 +877,184 @@
         assertThat(documents).hasSize(1);
     }
 
+    @Document(name = "Artist", parent = {Root.class})
+    static class Artist extends Root {
+        @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mName;
+        @Document.LongProperty(indexingType = INDEXING_TYPE_RANGE) long mAge;
+        @Document.StringProperty(indexingType = INDEXING_TYPE_PREFIXES) String mMostFamousWork;
+        @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mNationality;
+    }
+
+
+    // Indexed properties for Media type: {"name", "description", "leadActor.name", "leadActor.age"}
+    @Document(name = "Media", parent = {Root.class})
+    static class Media extends Root {
+        @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mName;
+        @Document.StringProperty(indexingType = INDEXING_TYPE_PREFIXES) String mDescription;
+        @Document.DocumentProperty(indexableNestedPropertiesList = {"name", "age"})
+        Artist mLeadActor;
+    }
+
+    // Indexed properties for Movie type: {"name", "description", "leadActor.name",
+    // "leadActor.nationality"}
+    // Movie does not index "leadActor.age" as the java class does not extend from Media
+    @Document(name = "Movie", parent = {Media.class})
+    static class Movie extends Root {
+        @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mName;
+        @Document.StringProperty(indexingType = INDEXING_TYPE_PREFIXES) String mDescription;
+        @Document.DocumentProperty(
+                indexableNestedPropertiesList = {"name", "nationality"},
+                inheritIndexableNestedPropertiesFromSuperclass = true)
+        Artist mLeadActor;
+    }
+
+    // Indexed properties for Documentary type: {"name", "description", "leadActor.name",
+    // "leadActor.age", "leadActor.mostFamousWork"}
+    // Documentary extends Media, so it should index all nested properties in Media.leadActor, as
+    // well as "leadActor.mostFamousWork"
+    @Document(name = "Documentary", parent = {Media.class})
+    static class Documentary extends Media {
+        @Document.DocumentProperty(
+                indexableNestedPropertiesList = {"mostFamousWork"},
+                inheritIndexableNestedPropertiesFromSuperclass = true)
+        Artist mLeadActor;
+        @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mEvent;
+    }
+
+    // Indexed properties for DocumentaryMovie type: {"name", "description", "leadActor.name",
+    // "leadActor.age", "leadActor.mostFamousWork"}
+    // Documentary extends Media, so it should index all nested properties in Media.leadActor, but
+    // should not index "leadActor.nationality" as it does not extend from Movie.
+    @Document(name = "DocumentaryMovie", parent = {Documentary.class, Movie.class})
+    static class DocumentaryMovie extends Documentary {
+        @Document.DocumentProperty(
+                indexableNestedPropertiesList = {},
+                inheritIndexableNestedPropertiesFromSuperclass = true)
+        Artist mLeadActor;
+        @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mDate;
+    }
+
+    @Test
+    public void testIndexableNestedPropertiesList() throws Exception {
+        assumeTrue(mSession.getFeatures().isFeatureSupported(
+                Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES));
+
+        mSession.setSchemaAsync(new SetSchemaRequest.Builder()
+                // Artist, Media, Movie and Documentary are added automatically as they are
+                // DocumentaryMovie's dependencies.
+                .addDocumentClasses(DocumentaryMovie.class)
+                // Add an unrelated schema type
+                .addDocumentClasses(Gift.class)
+                .build()).get();
+
+
+        // Create documents
+        Artist actor = new Artist();
+        actor.mNamespace = "namespace";
+        actor.mId = "id1";
+        actor.mName = "actor";
+        actor.mAge = 30;
+        actor.mMostFamousWork = "famousWork";
+        actor.mNationality = "nationality";
+
+        Media media = new Media();
+        media.mNamespace = "namespace";
+        media.mId = "id2";
+        media.mName = "media";
+        media.mDescription = "mediaDescription";
+        media.mLeadActor = actor;
+
+        Movie movie = new Movie();
+        movie.mNamespace = "namespace";
+        movie.mId = "id3";
+        movie.mName = "movie";
+        movie.mDescription = "movieDescription";
+        movie.mLeadActor = actor;
+
+        Documentary documentary = new Documentary();
+        documentary.mNamespace = "namespace";
+        documentary.mId = "id4";
+        documentary.mName = "documentary";
+        documentary.mDescription = "documentaryDescription";
+        documentary.mLeadActor = actor;
+        documentary.mEvent = "documentaryEvent";
+
+        DocumentaryMovie documentaryMovie = new DocumentaryMovie();
+        documentaryMovie.mNamespace = "namespace";
+        documentaryMovie.mId = "id5";
+        documentaryMovie.mName = "documentaryMovie";
+        documentaryMovie.mDescription = "documentaryMovieDescription";
+        documentaryMovie.mLeadActor = actor;
+        documentaryMovie.mEvent = "documentaryMovieEvent";
+        documentaryMovie.mDate = "2023-10-12";
+
+        checkIsBatchResultSuccess(mSession.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addDocuments(actor, media, movie, documentary, documentaryMovie)
+                        .build()));
+
+        // Query for all documents
+        SearchResults searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .build());
+        List documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(5);
+
+
+        // Query for "actor" should retrieve all documents.
+        searchResults = mSession.search("actor",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(5);
+
+        // Query for "leadActor.age == 30" should retrieve Media, Documentary and DocumentaryMovie
+        searchResults = mSession.search("leadActor.age == 30",
+                new SearchSpec.Builder()
+                        .setNumericSearchEnabled(true)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(3);
+        assertThat(documents.get(0).getId()).isEqualTo("id5");  // documentaryMovie
+        assertThat(documents.get(1).getId()).isEqualTo("id4");  // documentary
+        assertThat(documents.get(2).getId()).isEqualTo("id2");  // media
+
+        // Query for "famous" should retrieve Actor, Documentary and DocumentaryMovie
+        searchResults = mSession.search("famous",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(3);
+        assertThat(documents.get(0).getId()).isEqualTo("id5");  // documentaryMovie
+        assertThat(documents.get(1).getId()).isEqualTo("id4");  // documentary
+        assertThat(documents.get(2).getId()).isEqualTo("id1");  // actor
+
+        // Query for "nationality" should retrieve Actor and Movie.
+        searchResults = mSession.search("nationality",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        assertThat(documents.get(0).getId()).isEqualTo("id3");  // movie
+        assertThat(documents.get(1).getId()).isEqualTo("id1");  // actor
+    }
+
+    @Test
+    public void testIndexableNestedPropertiesList_notSupported() {
+        assumeFalse(mSession.getFeatures().isFeatureSupported(
+                Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES));
+
+        assertThrows(UnsupportedOperationException.class,
+                () -> mSession.setSchemaAsync(
+                        new SetSchemaRequest.Builder()
+                                .addDocumentClasses(DocumentaryMovie.class)
+                                .build()));
+    }
+
     // A class that some properties are annotated via getters without backing fields.
     @Document
     static class FakeMessage {
@@ -1640,6 +1820,344 @@
         assertThat(documents).containsExactly(businessGeneric);
     }
 
+    @Document(name = "Event", parent = InterfaceRoot.class)
+    interface Event extends InterfaceRoot {
+        @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS)
+        String getName();
+
+        @Document.DocumentProperty(indexableNestedPropertiesList = {"name", "age"})
+        Artist getPerformer();
+
+        @Document.BuilderProducer
+        class Builder {
+            String mId;
+            String mNamespace;
+            long mCreationTimestamp;
+            String mName;
+            Artist mPerformer;
+
+            Builder(String id, String namespace) {
+                mId = id;
+                mNamespace = namespace;
+            }
+
+            public Event build() {
+                return new EventImpl(mId, mNamespace, mCreationTimestamp, mName, mPerformer);
+            }
+
+            public Event.Builder setCreationTimestamp(long creationTimestamp) {
+                mCreationTimestamp = creationTimestamp;
+                return this;
+            }
+
+            public Event.Builder setName(String name) {
+                mName = name;
+                return this;
+            }
+
+            public Event.Builder setPerformer(Artist performer) {
+                mPerformer = performer;
+                return this;
+            }
+        }
+    }
+
+    @Document(name = "Charity", parent = InterfaceRoot.class)
+    interface Charity extends InterfaceRoot {
+        @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS)
+        String getName();
+
+        @Document.DocumentProperty(indexableNestedPropertiesList = {"name", "nationality"})
+        Artist getPerformer();
+
+        @Document.BuilderProducer
+        class Builder {
+            String mId;
+            String mNamespace;
+            long mCreationTimestamp;
+            String mName;
+            Artist mPerformer;
+
+            Builder(String id, String namespace) {
+                mId = id;
+                mNamespace = namespace;
+            }
+
+            public Charity build() {
+                return new CharityImpl(mId, mNamespace, mCreationTimestamp, mName, mPerformer);
+            }
+
+            public Charity.Builder setCreationTimestamp(long creationTimestamp) {
+                mCreationTimestamp = creationTimestamp;
+                return this;
+            }
+
+            public Charity.Builder setName(String name) {
+                mName = name;
+                return this;
+            }
+
+            public Charity.Builder setPerformer(Artist performer) {
+                mPerformer = performer;
+                return this;
+            }
+        }
+    }
+
+    @Document(name = "CharityEvent", parent = {Event.class, Charity.class})
+    interface CharityEvent extends Event, Charity {
+        @Document.StringProperty(indexingType = INDEXING_TYPE_PREFIXES)
+        String getDescription();
+
+        @Document.DocumentProperty(inheritIndexableNestedPropertiesFromSuperclass = true)
+        Artist getPerformer();
+
+        @Document.BuilderProducer
+        class Builder {
+            String mId;
+            String mNamespace;
+            long mCreationTimestamp;
+            String mName;
+            String mDescription;
+            Artist mPerformer;
+
+            Builder(String id, String namespace) {
+                mId = id;
+                mNamespace = namespace;
+            }
+
+            public CharityEvent build() {
+                return new CharityEventImpl(mId, mNamespace, mCreationTimestamp, mName,
+                        mDescription, mPerformer);
+            }
+
+            public CharityEvent.Builder setCreationTimestamp(long creationTimestamp) {
+                mCreationTimestamp = creationTimestamp;
+                return this;
+            }
+
+            public CharityEvent.Builder setName(String name) {
+                mName = name;
+                return this;
+            }
+
+            public CharityEvent.Builder setDescription(String description) {
+                mDescription = description;
+                return this;
+            }
+
+            public CharityEvent.Builder setPerformer(Artist performer) {
+                mPerformer = performer;
+                return this;
+            }
+        }
+    }
+
+
+    static class EventImpl implements Event {
+        String mId;
+        String mNamespace;
+        long mCreationTimestamp;
+        String mName;
+        Artist mPerformer;
+
+        EventImpl(String id, String namespace, long creationTimestamp, String name,
+                Artist performer) {
+            mId = id;
+            mNamespace = namespace;
+            mCreationTimestamp = creationTimestamp;
+            mName = name;
+            mPerformer = performer;
+        }
+
+        @Override
+        public String getId() {
+            return mId;
+        }
+
+        @Override
+        public String getNamespace() {
+            return mNamespace;
+        }
+
+        @Override
+        public long getCreationTimestamp() {
+            return mCreationTimestamp;
+        }
+
+        @Override
+        public String getName() {
+            return mName;
+        }
+
+        @Override
+        public Artist getPerformer() {
+            return mPerformer;
+        }
+    }
+
+    static class CharityImpl implements Charity {
+        String mId;
+        String mNamespace;
+        long mCreationTimestamp;
+        String mName;
+        Artist mPerformer;
+
+        CharityImpl(String id, String namespace, long creationTimestamp, String name,
+                Artist performer) {
+            mId = id;
+            mNamespace = namespace;
+            mCreationTimestamp = creationTimestamp;
+            mName = name;
+            mPerformer = performer;
+        }
+
+        @Override
+        public String getId() {
+            return mId;
+        }
+
+        @Override
+        public String getNamespace() {
+            return mNamespace;
+        }
+
+        @Override
+        public long getCreationTimestamp() {
+            return mCreationTimestamp;
+        }
+
+        @Override
+        public String getName() {
+            return mName;
+        }
+
+        @Override
+        public Artist getPerformer() {
+            return mPerformer;
+        }
+    }
+
+    // CharityEventImpl.performer's nested properties: {performer.name, performer.age,
+    // performer.nationality}
+    static class CharityEventImpl implements CharityEvent {
+        String mId;
+        String mNamespace;
+        long mCreationTimestamp;
+        String mName;
+        String mDescription;
+        Artist mPerformer;
+
+        CharityEventImpl(String id, String namespace, long creationTimestamp, String name,
+                String description, Artist performer) {
+            mId = id;
+            mNamespace = namespace;
+            mCreationTimestamp = creationTimestamp;
+            mName = name;
+            mDescription = description;
+            mPerformer = performer;
+        }
+
+        public String getId() {
+            return mId;
+        }
+
+        public String getNamespace() {
+            return mNamespace;
+        }
+
+        public long getCreationTimestamp() {
+            return mCreationTimestamp;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public Artist getPerformer() {
+            return mPerformer;
+        }
+
+        public String getDescription() {
+            return null;
+        }
+    }
+
+    @Test
+    public void testIndexableNestedPropertiesListForInterface() throws Exception {
+        assumeTrue(mSession.getFeatures().isFeatureSupported(
+                Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES));
+
+        mSession.setSchemaAsync(new SetSchemaRequest.Builder()
+                .addDocumentClasses(CharityEventImpl.class)
+                // Add an unrelated schema type
+                .addDocumentClasses(Gift.class)
+                .build()).get();
+
+
+        // Create documents
+        Artist performer = new Artist();
+        performer.mNamespace = "namespace";
+        performer.mId = "id1";
+        performer.mName = "performer";
+        performer.mAge = 30;
+        performer.mMostFamousWork = "famousWork";
+        performer.mNationality = "nationality";
+
+        CharityEventImpl charityEvent = new CharityEventImpl("id2", "namespace", 0, "charityEvent",
+                "charityEventDescription", performer);
+
+        checkIsBatchResultSuccess(mSession.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addDocuments(performer, charityEvent)
+                        .build()));
+
+        // Query for all documents
+        SearchResults searchResults = mSession.search("",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .build());
+        List documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+
+
+        // Query for "performer" should retrieve all documents.
+        searchResults = mSession.search("performer",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+
+        // Query for "performer.age == 30" should retrieve CharityEvent
+        searchResults = mSession.search("performer.age == 30",
+                new SearchSpec.Builder()
+                        .setNumericSearchEnabled(true)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents.get(0).getId()).isEqualTo("id2");  // charityEvent
+
+        // Query for "famous" should retrieve Performer
+        searchResults = mSession.search("famous",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents.get(0).getId()).isEqualTo("id1");  // performer
+
+        // Query for "nationality" should retrieve Performer and CharityEvent.
+        searchResults = mSession.search("nationality",
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        assertThat(documents.get(0).getId()).isEqualTo("id2");  // charityEvent
+        assertThat(documents.get(1).getId()).isEqualTo("id1");  // performer
+    }
+
     @Test
     public void testAppSearchDocumentClassMap() throws Exception {
         // Before this test, AppSearch's annotation processor has already generated the maps for
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java
index 8e802e6..59415fb 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java
@@ -332,14 +332,64 @@
         String name() default "";
 
         /**
-         * Configures whether fields in the nested document should be indexed.
+         * Configures whether all fields in the nested document should be indexed.
          *
          * 

If false, the nested document's properties are not indexed regardless of its own - * schema. + * schema, unless {@link #indexableNestedPropertiesList()} is used to index a subset of + * properties from the nested document. + * + *

{@link IllegalArgumentException} will be thrown during setSchema if set to true and + * defining a non-empty list for {@link #indexableNestedPropertiesList()} */ boolean indexNestedProperties() default false; /** + * The list of properties in the nested document to index. The property will be indexed + * according to its indexing configurations in the document's schema definition. + * + *

{@link #indexNestedProperties} is required to be false if this list is non-empty. + * {@link IllegalArgumentException} will be thrown during setSchema if this condition is + * not met. + * + * @see + * AppSearchSchema.DocumentPropertyConfig.Builder#addIndexableNestedProperties(Collection) + */ + String[] indexableNestedPropertiesList() default {}; + + /** + * Configures whether to inherit the indexable nested properties list from the Document's + * superclass type definition. When set to true, the indexable property paths will be + * a union of the paths specified in {@link #indexableNestedPropertiesList()} and any + * path specified in the document class's superclass or inherited interfaces. + * Effectively, this is a no-op if none of the document superclasses specify a path for + * this document property. + * + *

Ex. Consider the following Document classes: + *


+         * {@code
+         * @Document
+         * class Person {
+         *   @Document.DocumentProperty(indexableNestedPropertiesList = {"streetName", "zipcode"})
+         *   Address livesAt;
+         * }
+         * @Document
+         * class Artist extends Person {
+         *   @Document.DocumentProperty(
+         *     indexableNestedPropertiesList = {"country"},
+         *     inheritIndexableNestedPropertiesFromSuperclass = true
+         *   )
+         *   Address livesAt;
+         * }
+         * }
+         * 
+ * + *

By setting 'inheritIndexableNestedPropertiesFromSuperclass = true', Artist.livesAt + * inherits the indexable nested properties defined by its parent class's livesAt field + * (Person.livesAt) and indexes all three fields: {streetName, zipCode, country} + */ + boolean inheritIndexableNestedPropertiesFromSuperclass() default false; + + /** * Configures whether this property must be specified for the document to be valid. * *

This attribute does not apply to properties of a repeated type (e.g. a list).

diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentClassCreationInfo.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentClassCreationInfo.java
index a2a4fd6..65c7b554 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentClassCreationInfo.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentClassCreationInfo.java
@@ -420,9 +420,7 @@
         }
 
         private static boolean isAnnotatedWithBuilderProducer(@NonNull Element element) {
-            return element.getAnnotationMirrors().stream()
-                    .anyMatch(annotation -> annotation.getAnnotationType().toString()
-                            .equals(BUILDER_PRODUCER_CLASS.canonicalName()));
+            return !IntrospectionHelper.getAnnotations(element, BUILDER_PRODUCER_CLASS).isEmpty();
         }
 
         /**
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
index a6c6a24..216d075 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
@@ -48,6 +48,9 @@
 /**
  * Processes @Document annotations.
  *
+ * @see AnnotatedGetterAndFieldAccumulator for the DocumentModel's invariants with regards to its
+ * getter and field definitions.
+ *
  * @exportToFramework:hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
index a458359..7884791 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
@@ -22,6 +22,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation;
+import androidx.appsearch.compiler.annotationwrapper.DocumentPropertyAnnotation;
+import androidx.appsearch.compiler.annotationwrapper.PropertyAnnotation;
 
 import com.google.auto.value.AutoValue;
 import com.squareup.javapoet.ClassName;
@@ -155,11 +158,58 @@
     @Nullable
     public static AnnotationMirror getDocumentAnnotation(@NonNull Element element) {
         Objects.requireNonNull(element);
-        for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
-            String annotationFq = annotation.getAnnotationType().toString();
-            if (IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS.canonicalName().equals(
-                    annotationFq)) {
-                return annotation;
+        List annotations = getAnnotations(element,
+                DOCUMENT_ANNOTATION_CLASS);
+        if (annotations.isEmpty()) {
+            return null;
+        } else {
+            return annotations.get(0);
+        }
+    }
+
+    /**
+     * Returns a list of annotations of a given kind from the input element's annotations,
+     * specified by the annotation's class name. Returns null if no annotation of such kind is
+     * found.
+     */
+    @NonNull
+    public static List getAnnotations(@NonNull Element element,
+            @NonNull ClassName className) {
+        Objects.requireNonNull(element);
+        Objects.requireNonNull(className);
+        return element.getAnnotationMirrors()
+                .stream()
+                .filter(annotation -> annotation.getAnnotationType().toString()
+                        .equals(className.canonicalName()))
+                .toList();
+    }
+
+    /**
+     * Returns the document property annotation that matches the given property name from a given
+     * class or interface element.
+     *
+     * 

Returns null if the property cannot be found in the class or interface, or if the + * property matching the property name is not a document property. + */ + @Nullable + public DocumentPropertyAnnotation getDocumentPropertyAnnotation( + @NonNull TypeElement clazz, @NonNull String propertyName) throws ProcessingException { + Objects.requireNonNull(clazz); + Objects.requireNonNull(propertyName); + for (Element enclosedElement : clazz.getEnclosedElements()) { + AnnotatedGetterOrField getterOrField = + AnnotatedGetterOrField.tryCreateFor(enclosedElement, mEnv); + if (getterOrField == null || !(getterOrField.getAnnotation().getPropertyKind() + == PropertyAnnotation.Kind.DATA_PROPERTY)) { + continue; + } + if (((DataPropertyAnnotation) getterOrField.getAnnotation()).getDataPropertyKind() + == DataPropertyAnnotation.Kind.DOCUMENT_PROPERTY) { + DocumentPropertyAnnotation documentPropertyAnnotation = + (DocumentPropertyAnnotation) getterOrField.getAnnotation(); + if (documentPropertyAnnotation.getName().equals(propertyName)) { + return documentPropertyAnnotation; + } } } return null;

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 288a61b..9199c47 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
@@ -21,6 +21,10 @@
 import static androidx.appsearch.compiler.IntrospectionHelper.PROPERTY_CONFIG_CLASS;
 import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentClassFactoryForClass;
 
+import static com.google.auto.common.MoreTypes.asTypeElement;
+
+import static javax.lang.model.type.TypeKind.DECLARED;
+
 import androidx.annotation.NonNull;
 import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation;
 import androidx.appsearch.compiler.annotationwrapper.DocumentPropertyAnnotation;
@@ -36,8 +40,14 @@
 import com.squareup.javapoet.TypeSpec;
 import com.squareup.javapoet.WildcardTypeName;
 
+import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
 
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
@@ -47,6 +57,7 @@
 /** Generates java code for an {@link androidx.appsearch.app.AppSearchSchema}. */
 class SchemaCodeGenerator {
     private final DocumentModel mModel;
+    private final IntrospectionHelper mHelper;
     private final LinkedHashSet mDependencyDocumentClasses;
 
     public static void generate(
@@ -58,6 +69,7 @@
 
     private SchemaCodeGenerator(@NonNull DocumentModel model, @NonNull ProcessingEnvironment env) {
         mModel = model;
+        mHelper = new IntrospectionHelper(env);
         mDependencyDocumentClasses = computeDependencyClasses(model, env);
     }
 
@@ -206,6 +218,12 @@
                 DocumentPropertyAnnotation documentPropertyAnnotation =
                         (DocumentPropertyAnnotation) annotation;
                 codeBlock.add(createSetShouldIndexNestedPropertiesExpr(documentPropertyAnnotation));
+                Set indexableNestedProperties = getAllIndexableNestedProperties(
+                        documentPropertyAnnotation);
+                for (String propertyPath : indexableNestedProperties) {
+                    codeBlock.add(
+                            CodeBlock.of("\n.addIndexableNestedProperties($L)", propertyPath));
+                }
                 break;
             case LONG_PROPERTY:
                 LongPropertyAnnotation longPropertyAnnotation = (LongPropertyAnnotation) annotation;
@@ -223,6 +241,59 @@
                 .build();
     }
 
+
+    /**
+     * Finds all indexable nested properties for the given type class and document property
+     * annotation. This includes indexable nested properties that should be inherited from the
+     * type's parent.
+     */
+    private Set getAllIndexableNestedProperties(
+            @NonNull DocumentPropertyAnnotation documentPropertyAnnotation)
+            throws ProcessingException {
+        Set indexableNestedProperties = new HashSet<>(
+                documentPropertyAnnotation.getIndexableNestedPropertiesList());
+
+        if (documentPropertyAnnotation.shouldInheritIndexableNestedPropertiesFromSuperClass()) {
+            // List of classes to expand into parent classes to search for the property annotation
+            Queue classesToExpand = new ArrayDeque<>();
+            Set visited = new HashSet<>();
+            classesToExpand.add(mModel.getClassElement());
+            while (!classesToExpand.isEmpty()) {
+                TypeElement currentClass = classesToExpand.poll();
+                if (visited.contains(currentClass)) {
+                    continue;
+                }
+                visited.add(currentClass);
+                // Look for the document property annotation in the class's parent classes
+                List parentTypes = new ArrayList<>();
+                parentTypes.add(currentClass.getSuperclass());
+                parentTypes.addAll(currentClass.getInterfaces());
+                for (TypeMirror parent : parentTypes) {
+                    if (!parent.getKind().equals(DECLARED)) {
+                        continue;
+                    }
+                    TypeElement parentElement = asTypeElement(parent);
+                    DocumentPropertyAnnotation annotation = mHelper.getDocumentPropertyAnnotation(
+                            parentElement, documentPropertyAnnotation.getName());
+                    if (annotation == null) {
+                        // The property is not found in this level. Continue searching in one level
+                        // above as the property could still be defined for this level by class
+                        // inheritance.
+                        classesToExpand.add(parentElement);
+                    } else {
+                        indexableNestedProperties.addAll(
+                                annotation.getIndexableNestedPropertiesList());
+                        if (annotation.shouldInheritIndexableNestedPropertiesFromSuperClass()) {
+                            // Continue searching in the parent class's parents
+                            classesToExpand.add(parentElement);
+                        }
+                    }
+                }
+            }
+        }
+        return indexableNestedProperties;
+    }
+
     /**
      * Creates an expr like {@code .setCardinality(PropertyConfig.CARDINALITY_REPEATED)}.
      */
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java
index 4bf1789..3807b17 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java
@@ -23,8 +23,11 @@
 import androidx.appsearch.compiler.IntrospectionHelper;
 
 import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.ClassName;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 import javax.lang.model.type.TypeMirror;
@@ -57,10 +60,19 @@
     static DocumentPropertyAnnotation parse(
             @NonNull Map annotationParams, @NonNull String defaultName) {
         String name = (String) annotationParams.get("name");
+        List indexableNestedPropertiesList = new ArrayList<>();
+        Object indexableList = annotationParams.get("indexableNestedPropertiesList");
+        if (indexableList instanceof List) {
+            for (Object property : (List) indexableList) {
+                indexableNestedPropertiesList.add(property.toString());
+            }
+        }
         return new AutoValue_DocumentPropertyAnnotation(
                 name.isEmpty() ? defaultName : name,
                 (boolean) annotationParams.get("required"),
-                (boolean) annotationParams.get("indexNestedProperties"));
+                (boolean) annotationParams.get("indexNestedProperties"),
+                ImmutableList.copyOf(indexableNestedPropertiesList),
+                (boolean) annotationParams.get("inheritIndexableNestedPropertiesFromSuperclass"));
     }
 
     /**
@@ -68,6 +80,19 @@
      */
     public abstract boolean shouldIndexNestedProperties();
 
+    /**
+     * Returns the list of nested properties to index for the nested document other than the
+     * properties inherited from the type's parent.
+     */
+    @NonNull
+    public abstract ImmutableList getIndexableNestedPropertiesList();
+
+    /**
+     * Specifies whether to inherit the parent class's definition for the indexable nested
+     * properties list.
+     */
+    public abstract boolean shouldInheritIndexableNestedPropertiesFromSuperClass();
+
     @NonNull
     @Override
     public final Kind getDataPropertyKind() {
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 18148d7..0b5e501 100644
--- a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
+++ b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
@@ -1828,6 +1828,341 @@
     }
 
     @Test
+    public void testIndexableNestedPropertiesListSimple() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Address {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty long streetNumber;\n"
+                        + "  @Document.StringProperty String streetName;\n"
+                        + "  @Document.StringProperty String state;\n"
+                        + "  @Document.LongProperty long zipCode;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Person {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String name;\n"
+                        + "  @Document.DocumentProperty(indexableNestedPropertiesList ="
+                        + "    {\"streetNumber\", \"streetName\"}) Address livesAt;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        checkResultContains("Person.java", "addIndexableNestedProperties(\"streetNumber\")");
+        checkResultContains("Person.java", "addIndexableNestedProperties(\"streetName\")");
+
+        checkEqualsGolden("Person.java");
+    }
+
+    @Test
+    public void testIndexableNestedPropertiesListEmpty() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Address {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty long streetNumber;\n"
+                        + "  @Document.StringProperty String streetName;\n"
+                        + "  @Document.StringProperty String state;\n"
+                        + "  @Document.LongProperty long zipCode;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Person {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String name;\n"
+                        + "  @Document.DocumentProperty(indexableNestedPropertiesList = {})"
+                        + "  Address livesAt;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+        checkResultDoesNotContain("Person.java", "addIndexableNestedProperties");
+        checkEqualsGolden("Person.java");
+    }
+
+    @Test
+    public void testIndexableNestedPropertiesListInheritSuperclassTrue() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Address {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty long streetNumber;\n"
+                        + "  @Document.StringProperty String streetName;\n"
+                        + "  @Document.StringProperty String state;\n"
+                        + "  @Document.LongProperty long zipCode;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Person {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String name;\n"
+                        + "  @Document.DocumentProperty(indexableNestedPropertiesList ="
+                        + "    {\"streetNumber\", \"streetName\"}) Address livesAt;\n"
+                        + "}\n"
+                        + "@Document(name = \"Artist\", parent = {Person.class})\n"
+                        + "class Artist extends Person {\n"
+                        + "  @Document.StringProperty String mostFamousWork;\n"
+                        + "  @Document.DocumentProperty("
+                        + "    indexableNestedPropertiesList = {\"state\"},"
+                        + "    inheritIndexableNestedPropertiesFromSuperclass = true)"
+                        + "  Address livesAt;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        checkResultContains("Artist.java", "addIndexableNestedProperties(\"streetNumber\")");
+        checkResultContains("Artist.java", "addIndexableNestedProperties(\"streetName\")");
+        checkResultContains("Artist.java", "addIndexableNestedProperties(\"state\")");
+
+        checkEqualsGolden("Artist.java");
+    }
+
+    @Test
+    public void testIndexableNestedPropertiesListInheritSuperclassFalse() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Address {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty long streetNumber;\n"
+                        + "  @Document.StringProperty String streetName;\n"
+                        + "  @Document.StringProperty String state;\n"
+                        + "  @Document.LongProperty long zipCode;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Person {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String name;\n"
+                        + "  @Document.DocumentProperty(indexableNestedPropertiesList ="
+                        + "    {\"streetNumber\", \"streetName\"}) Address livesAt;\n"
+                        + "}\n"
+                        + "@Document(name = \"Artist\", parent = {Person.class})\n"
+                        + "class Artist extends Person {\n"
+                        + "  @Document.StringProperty String mostFamousWork;\n"
+                        + "  @Document.DocumentProperty("
+                        + "    indexableNestedPropertiesList = {\"state\"},"
+                        + "    inheritIndexableNestedPropertiesFromSuperclass = false)"
+                        + "  Address livesAt;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        checkResultContains("Artist.java", "addIndexableNestedProperties(\"state\")");
+        checkResultDoesNotContain("Artist.java", "addIndexableNestedProperties(\"streetNumber\")");
+        checkResultDoesNotContain("Artist.java", "addIndexableNestedProperties(\"streetName\")");
+
+        checkEqualsGolden("Artist.java");
+    }
+
+    @Test
+    public void testIndexableNestedPropertiesListInheritWithMultipleParentsClasses()
+            throws Exception {
+        // Tests that the child class inherits nested properties from the parent correctly. When
+        // set to true, the field overridden by the child class should only inherit indexable
+        // nested properties form its java parent class (i.e. the superclass/interface which the
+        // child class extends from/implements).
+        // In this test case, Artist's parent class is Person, and ArtistEmployee's parent class
+        // is Artist. This means that ArtistEmployee.livesAt's indexable list should contain the
+        // properties s specified in Artist.livesAt. and Person.livesAt (since both Artist and
+        // ArtistEmployee sets inheritFromParent=true for this field). ArtistEmployee.livesAt
+        // should not inherit the indexable list from Employee.livesAt since Employee is not
+        // ArtistEmployee's java class parent.
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Address {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty long streetNumber;\n"
+                        + "  @Document.StringProperty String streetName;\n"
+                        + "  @Document.StringProperty String state;\n"
+                        + "  @Document.LongProperty long zipCode;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Person {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String name;\n"
+                        + "  @Document.DocumentProperty(indexableNestedPropertiesList ="
+                        + "    {\"streetNumber\", \"streetName\"}) Address livesAt;\n"
+                        + "}\n"
+                        + "@Document(name = \"Artist\", parent = {Person.class})\n"
+                        + "class Artist extends Person {\n"
+                        + "  @Document.StringProperty String mostFamousWork;\n"
+                        + "  @Document.DocumentProperty("
+                        + "    indexableNestedPropertiesList = {\"state\"},"
+                        + "    inheritIndexableNestedPropertiesFromSuperclass = true)"
+                        + "  Address livesAt;\n"
+                        + "}\n"
+                        + "@Document(name = \"Employee\", parent = {Person.class})\n"
+                        + "class Employee extends Person {\n"
+                        + "  @Document.DocumentProperty("
+                        + "    indexableNestedPropertiesList = {\"zipCode\"},"
+                        + "    inheritIndexableNestedPropertiesFromSuperclass = true)"
+                        + "  Address livesAt;\n"
+                        + "  @Document.DocumentProperty(indexableNestedPropertiesList ="
+                        + "    {\"zipCode\", \"streetName\"}) Address worksAt;\n"
+                        + "}\n"
+                        + "@Document(name = \"ArtistEmployee\", parent = {Artist.class,"
+                        + "Employee.class})\n"
+                        + "class ArtistEmployee extends Artist {\n"
+                        + "  @Document.StringProperty String mostFamousWork;\n"
+                        + "  @Document.DocumentProperty("
+                        + "    inheritIndexableNestedPropertiesFromSuperclass = true)"
+                        + "  Address livesAt;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        checkResultContains("ArtistEmployee.java",
+                "addIndexableNestedProperties(\"streetNumber\")");
+        checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"streetName\")");
+        checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"state\")");
+        // ArtistEmployee's indexable list should not contain 'zipCode' as  ArtistEmployee only
+        // extends Artist, which does not index zipCode
+        checkResultDoesNotContain("ArtistEmployee.java",
+                "addIndexableNestedProperties(\"zipCode\")");
+
+        checkEqualsGolden("ArtistEmployee.java");
+    }
+
+    @Test
+    public void testIndexableNestedPropertiesListImplicitInheritance() throws Exception {
+        // Tests that properties that are not declared in the child class itself but exists in
+        // the class due to java class inheritance indexes the correct indexable list.
+        // Artist.livesAt should be defined for Artist and index the same indexable properties as
+        // Person.livesAt.
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Address {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty long streetNumber;\n"
+                        + "  @Document.StringProperty String streetName;\n"
+                        + "  @Document.StringProperty String state;\n"
+                        + "  @Document.LongProperty long zipCode;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Person {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String name;\n"
+                        + "  @Document.DocumentProperty(indexableNestedPropertiesList ="
+                        + "    {\"streetNumber\", \"streetName\"}) Address livesAt;\n"
+                        + "}\n"
+                        + "@Document(name = \"Artist\", parent = {Person.class})\n"
+                        + "class Artist extends Person {\n"
+                        + "  @Document.StringProperty String mostFamousWork;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        checkResultContains("Artist.java",
+                "addIndexableNestedProperties(\"streetNumber\")");
+        checkResultContains("Artist.java", "addIndexableNestedProperties(\"streetName\")");
+
+        checkResultDoesNotContain("Artist.java",
+                "addIndexableNestedProperties(\"zipCode\")");
+        checkResultDoesNotContain("Artist.java", "addIndexableNestedProperties(\"state\")");
+
+        checkEqualsGolden("Artist.java");
+    }
+
+    @Test
+    public void testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels()
+            throws Exception {
+        // Tests that the indexable list is inherited correctly across multiple java inheritance
+        // levels.
+        // ArtistEmployee.livesAt should index the nested properties defined in Person.livesAt.
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Address {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty long streetNumber;\n"
+                        + "  @Document.StringProperty String streetName;\n"
+                        + "  @Document.StringProperty String state;\n"
+                        + "  @Document.LongProperty long zipCode;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Person {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String name;\n"
+                        + "  @Document.DocumentProperty(indexableNestedPropertiesList ="
+                        + "    {\"streetNumber\", \"streetName\"}) Address livesAt;\n"
+                        + "}\n"
+                        + "@Document(name = \"Artist\", parent = {Person.class})\n"
+                        + "class Artist extends Person {\n"
+                        + "  @Document.StringProperty String mostFamousWork;\n"
+                        + "}\n"
+                        + "@Document(name = \"ArtistEmployee\", parent = {Artist.class})\n"
+                        + "class ArtistEmployee extends Artist {\n"
+                        + "  @Document.StringProperty String worksAt;\n"
+                        + "  @Document.DocumentProperty("
+                        + "    inheritIndexableNestedPropertiesFromSuperclass = true)"
+                        + "  Address livesAt;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        checkResultContains("ArtistEmployee.java",
+                "addIndexableNestedProperties(\"streetNumber\")");
+        checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"streetName\")");
+
+        checkResultDoesNotContain("ArtistEmployee.java",
+                "addIndexableNestedProperties(\"zipCode\")");
+        checkResultDoesNotContain("ArtistEmployee.java", "addIndexableNestedProperties(\"state\")");
+
+        checkEqualsGolden("ArtistEmployee.java");
+    }
+
+    @Test
+    public void testIndexableNestedPropertiesListTopLevelInheritTrue() throws Exception {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "class Address {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.LongProperty long streetNumber;\n"
+                        + "  @Document.StringProperty String streetName;\n"
+                        + "  @Document.StringProperty String state;\n"
+                        + "  @Document.LongProperty long zipCode;\n"
+                        + "}\n"
+                        + "@Document\n"
+                        + "class Person {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.StringProperty String name;\n"
+                        + "  @Document.DocumentProperty(indexableNestedPropertiesList ="
+                        + "    {\"streetNumber\", \"streetName\"},"
+                        + "    inheritIndexableNestedPropertiesFromSuperclass = true)"
+                        + "  Address livesAt;\n"
+                        + "}\n"
+                        + "@Document(name = \"Artist\", parent = {Person.class})\n"
+                        + "class Artist extends Person {\n"
+                        + "  @Document.StringProperty String mostFamousWork;\n"
+                        + "  @Document.DocumentProperty(indexableNestedPropertiesList ="
+                        + "    {\"state\"}, inheritIndexableNestedPropertiesFromSuperclass = true)"
+                        + "  Address livesAt;\n"
+                        + "}\n"
+                        + "@Document(name = \"ArtistEmployee\", parent = {Artist.class})\n"
+                        + "class ArtistEmployee extends Artist {\n"
+                        + "  @Document.StringProperty String worksAt;\n"
+                        + "  @Document.DocumentProperty("
+                        + "    inheritIndexableNestedPropertiesFromSuperclass = true)"
+                        + "  Address livesAt;\n"
+                        + "}\n");
+        assertThat(compilation).succeededWithoutWarnings();
+
+        checkResultContains("ArtistEmployee.java",
+                "addIndexableNestedProperties(\"streetNumber\")");
+        checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"streetName\")");
+        checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"state\")");
+
+        checkResultDoesNotContain("ArtistEmployee.java",
+                "addIndexableNestedProperties(\"zipCode\")");
+
+        checkEqualsGolden("ArtistEmployee.java");
+    }
+
+    @Test
     public void testAnnotationOnClassGetter() throws Exception {
         Compilation compilation = compile(
                 "@Document\n"
@@ -3192,14 +3527,21 @@
     }
 
     private void checkResultContains(String className, String content) throws IOException {
-        // Get the actual file contents
+        String fileContents = getClassFileContents(className);
+        Truth.assertThat(fileContents).contains(content);
+    }
+
+    private void checkResultDoesNotContain(String className, String content) throws IOException {
+        String fileContents = getClassFileContents(className);
+        Truth.assertThat(fileContents).doesNotContain(content);
+    }
+
+    private String getClassFileContents(String className) throws IOException {
         File actualPackageDir = new File(mGenFilesDir, "com/example/appsearch");
         File actualPath =
                 new File(actualPackageDir, IntrospectionHelper.GEN_CLASS_PREFIX + className);
         Truth.assertWithMessage("Path " + actualPath + " is not a file")
                 .that(actualPath.isFile()).isTrue();
-        String actual = Files.asCharSource(actualPath, StandardCharsets.UTF_8).read();
-
-        Truth.assertThat(actual).contains(content);
+        return Files.asCharSource(actualPath, StandardCharsets.UTF_8).read();
     }
 }
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListEmpty.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListEmpty.JAVA
new file mode 100644
index 0000000..553f36b
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListEmpty.JAVA
@@ -0,0 +1,85 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Person implements DocumentClassFactory {
+  public static final String SCHEMA_NAME = "Person";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME)
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setShouldIndexNestedProperties(false)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List> getDependencyDocumentClasses() throws AppSearchException {
+    List> classSet = new ArrayList>();
+    classSet.add(Address.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Person document) throws AppSearchException {
+    GenericDocument.Builder builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String nameCopy = document.name;
+    if (nameCopy != null) {
+      builder.setPropertyString("name", nameCopy);
+    }
+    Address livesAtCopy = document.livesAt;
+    if (livesAtCopy != null) {
+      GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy);
+      builder.setPropertyDocument("livesAt", livesAtConv);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Person fromGenericDocument(GenericDocument genericDoc,
+      Map> documentClassMap) throws AppSearchException {
+    String namespaceConv = genericDoc.getNamespace();
+    String idConv = genericDoc.getId();
+    String[] nameCopy = genericDoc.getPropertyStringArray("name");
+    String nameConv = null;
+    if (nameCopy != null && nameCopy.length != 0) {
+      nameConv = nameCopy[0];
+    }
+    GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
+    Address livesAtConv = null;
+    if (livesAtCopy != null) {
+      livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+    }
+    Person document = new Person();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.name = nameConv;
+    document.livesAt = livesAtConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitInheritance.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitInheritance.JAVA
new file mode 100644
index 0000000..ac8da6b
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitInheritance.JAVA
@@ -0,0 +1,105 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Artist implements DocumentClassFactory {
+  public static final String SCHEMA_NAME = "Artist";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Person.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME)
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setShouldIndexNestedProperties(false)
+            .addIndexableNestedProperties("streetNumber")
+            .addIndexableNestedProperties("streetName")
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List> getDependencyDocumentClasses() throws AppSearchException {
+    List> classSet = new ArrayList>();
+    classSet.add(Person.class);
+    classSet.add(Address.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Artist document) throws AppSearchException {
+    GenericDocument.Builder builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String nameCopy = document.name;
+    if (nameCopy != null) {
+      builder.setPropertyString("name", nameCopy);
+    }
+    Address livesAtCopy = document.livesAt;
+    if (livesAtCopy != null) {
+      GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy);
+      builder.setPropertyDocument("livesAt", livesAtConv);
+    }
+    String mostFamousWorkCopy = document.mostFamousWork;
+    if (mostFamousWorkCopy != null) {
+      builder.setPropertyString("mostFamousWork", mostFamousWorkCopy);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Artist fromGenericDocument(GenericDocument genericDoc,
+      Map> documentClassMap) throws AppSearchException {
+    String namespaceConv = genericDoc.getNamespace();
+    String idConv = genericDoc.getId();
+    String[] nameCopy = genericDoc.getPropertyStringArray("name");
+    String nameConv = null;
+    if (nameCopy != null && nameCopy.length != 0) {
+      nameConv = nameCopy[0];
+    }
+    GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
+    Address livesAtConv = null;
+    if (livesAtCopy != null) {
+      livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+    }
+    String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
+    String mostFamousWorkConv = null;
+    if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) {
+      mostFamousWorkConv = mostFamousWorkCopy[0];
+    }
+    Artist document = new Artist();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.name = nameConv;
+    document.livesAt = livesAtConv;
+    document.mostFamousWork = mostFamousWorkConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels.JAVA
new file mode 100644
index 0000000..ce3287d
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels.JAVA
@@ -0,0 +1,121 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__ArtistEmployee implements DocumentClassFactory {
+  public static final String SCHEMA_NAME = "ArtistEmployee";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Artist.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME)
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setShouldIndexNestedProperties(false)
+            .addIndexableNestedProperties("streetNumber")
+            .addIndexableNestedProperties("streetName")
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("worksAt")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List> getDependencyDocumentClasses() throws AppSearchException {
+    List> classSet = new ArrayList>();
+    classSet.add(Artist.class);
+    classSet.add(Address.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(ArtistEmployee document) throws AppSearchException {
+    GenericDocument.Builder builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String nameCopy = document.name;
+    if (nameCopy != null) {
+      builder.setPropertyString("name", nameCopy);
+    }
+    Address livesAtCopy = document.livesAt;
+    if (livesAtCopy != null) {
+      GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy);
+      builder.setPropertyDocument("livesAt", livesAtConv);
+    }
+    String mostFamousWorkCopy = document.mostFamousWork;
+    if (mostFamousWorkCopy != null) {
+      builder.setPropertyString("mostFamousWork", mostFamousWorkCopy);
+    }
+    String worksAtCopy = document.worksAt;
+    if (worksAtCopy != null) {
+      builder.setPropertyString("worksAt", worksAtCopy);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public ArtistEmployee fromGenericDocument(GenericDocument genericDoc,
+      Map> documentClassMap) throws AppSearchException {
+    String namespaceConv = genericDoc.getNamespace();
+    String idConv = genericDoc.getId();
+    String[] nameCopy = genericDoc.getPropertyStringArray("name");
+    String nameConv = null;
+    if (nameCopy != null && nameCopy.length != 0) {
+      nameConv = nameCopy[0];
+    }
+    GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
+    Address livesAtConv = null;
+    if (livesAtCopy != null) {
+      livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+    }
+    String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
+    String mostFamousWorkConv = null;
+    if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) {
+      mostFamousWorkConv = mostFamousWorkCopy[0];
+    }
+    String[] worksAtCopy = genericDoc.getPropertyStringArray("worksAt");
+    String worksAtConv = null;
+    if (worksAtCopy != null && worksAtCopy.length != 0) {
+      worksAtConv = worksAtCopy[0];
+    }
+    ArtistEmployee document = new ArtistEmployee();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.name = nameConv;
+    document.livesAt = livesAtConv;
+    document.mostFamousWork = mostFamousWorkConv;
+    document.worksAt = worksAtConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassFalse.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassFalse.JAVA
new file mode 100644
index 0000000..e4d5805
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassFalse.JAVA
@@ -0,0 +1,104 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Artist implements DocumentClassFactory {
+  public static final String SCHEMA_NAME = "Artist";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Person.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME)
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setShouldIndexNestedProperties(false)
+            .addIndexableNestedProperties("state")
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List> getDependencyDocumentClasses() throws AppSearchException {
+    List> classSet = new ArrayList>();
+    classSet.add(Person.class);
+    classSet.add(Address.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Artist document) throws AppSearchException {
+    GenericDocument.Builder builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String nameCopy = document.name;
+    if (nameCopy != null) {
+      builder.setPropertyString("name", nameCopy);
+    }
+    Address livesAtCopy = document.livesAt;
+    if (livesAtCopy != null) {
+      GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy);
+      builder.setPropertyDocument("livesAt", livesAtConv);
+    }
+    String mostFamousWorkCopy = document.mostFamousWork;
+    if (mostFamousWorkCopy != null) {
+      builder.setPropertyString("mostFamousWork", mostFamousWorkCopy);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Artist fromGenericDocument(GenericDocument genericDoc,
+      Map> documentClassMap) throws AppSearchException {
+    String namespaceConv = genericDoc.getNamespace();
+    String idConv = genericDoc.getId();
+    String[] nameCopy = genericDoc.getPropertyStringArray("name");
+    String nameConv = null;
+    if (nameCopy != null && nameCopy.length != 0) {
+      nameConv = nameCopy[0];
+    }
+    GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
+    Address livesAtConv = null;
+    if (livesAtCopy != null) {
+      livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+    }
+    String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
+    String mostFamousWorkConv = null;
+    if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) {
+      mostFamousWorkConv = mostFamousWorkCopy[0];
+    }
+    Artist document = new Artist();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.name = nameConv;
+    document.livesAt = livesAtConv;
+    document.mostFamousWork = mostFamousWorkConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassTrue.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassTrue.JAVA
new file mode 100644
index 0000000..1620454
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassTrue.JAVA
@@ -0,0 +1,106 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Artist implements DocumentClassFactory {
+  public static final String SCHEMA_NAME = "Artist";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Person.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME)
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setShouldIndexNestedProperties(false)
+            .addIndexableNestedProperties("streetNumber")
+            .addIndexableNestedProperties("state")
+            .addIndexableNestedProperties("streetName")
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List> getDependencyDocumentClasses() throws AppSearchException {
+    List> classSet = new ArrayList>();
+    classSet.add(Person.class);
+    classSet.add(Address.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Artist document) throws AppSearchException {
+    GenericDocument.Builder builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String nameCopy = document.name;
+    if (nameCopy != null) {
+      builder.setPropertyString("name", nameCopy);
+    }
+    Address livesAtCopy = document.livesAt;
+    if (livesAtCopy != null) {
+      GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy);
+      builder.setPropertyDocument("livesAt", livesAtConv);
+    }
+    String mostFamousWorkCopy = document.mostFamousWork;
+    if (mostFamousWorkCopy != null) {
+      builder.setPropertyString("mostFamousWork", mostFamousWorkCopy);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Artist fromGenericDocument(GenericDocument genericDoc,
+      Map> documentClassMap) throws AppSearchException {
+    String namespaceConv = genericDoc.getNamespace();
+    String idConv = genericDoc.getId();
+    String[] nameCopy = genericDoc.getPropertyStringArray("name");
+    String nameConv = null;
+    if (nameCopy != null && nameCopy.length != 0) {
+      nameConv = nameCopy[0];
+    }
+    GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
+    Address livesAtConv = null;
+    if (livesAtCopy != null) {
+      livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+    }
+    String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
+    String mostFamousWorkConv = null;
+    if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) {
+      mostFamousWorkConv = mostFamousWorkCopy[0];
+    }
+    Artist document = new Artist();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.name = nameConv;
+    document.livesAt = livesAtConv;
+    document.mostFamousWork = mostFamousWorkConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritWithMultipleParentsClasses.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritWithMultipleParentsClasses.JAVA
new file mode 100644
index 0000000..3c4c645
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritWithMultipleParentsClasses.JAVA
@@ -0,0 +1,108 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__ArtistEmployee implements DocumentClassFactory {
+  public static final String SCHEMA_NAME = "ArtistEmployee";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Artist.SCHEMA_NAME)
+          .addParentType($$__AppSearch__Employee.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME)
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setShouldIndexNestedProperties(false)
+            .addIndexableNestedProperties("streetNumber")
+            .addIndexableNestedProperties("state")
+            .addIndexableNestedProperties("streetName")
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List> getDependencyDocumentClasses() throws AppSearchException {
+    List> classSet = new ArrayList>();
+    classSet.add(Artist.class);
+    classSet.add(Employee.class);
+    classSet.add(Address.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(ArtistEmployee document) throws AppSearchException {
+    GenericDocument.Builder builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String nameCopy = document.name;
+    if (nameCopy != null) {
+      builder.setPropertyString("name", nameCopy);
+    }
+    Address livesAtCopy = document.livesAt;
+    if (livesAtCopy != null) {
+      GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy);
+      builder.setPropertyDocument("livesAt", livesAtConv);
+    }
+    String mostFamousWorkCopy = document.mostFamousWork;
+    if (mostFamousWorkCopy != null) {
+      builder.setPropertyString("mostFamousWork", mostFamousWorkCopy);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public ArtistEmployee fromGenericDocument(GenericDocument genericDoc,
+      Map> documentClassMap) throws AppSearchException {
+    String namespaceConv = genericDoc.getNamespace();
+    String idConv = genericDoc.getId();
+    String[] nameCopy = genericDoc.getPropertyStringArray("name");
+    String nameConv = null;
+    if (nameCopy != null && nameCopy.length != 0) {
+      nameConv = nameCopy[0];
+    }
+    GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
+    Address livesAtConv = null;
+    if (livesAtCopy != null) {
+      livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+    }
+    String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
+    String mostFamousWorkConv = null;
+    if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) {
+      mostFamousWorkConv = mostFamousWorkCopy[0];
+    }
+    ArtistEmployee document = new ArtistEmployee();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.name = nameConv;
+    document.livesAt = livesAtConv;
+    document.mostFamousWork = mostFamousWorkConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListSimple.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListSimple.JAVA
new file mode 100644
index 0000000..d7fe77d
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListSimple.JAVA
@@ -0,0 +1,87 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__Person implements DocumentClassFactory {
+  public static final String SCHEMA_NAME = "Person";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME)
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setShouldIndexNestedProperties(false)
+            .addIndexableNestedProperties("streetNumber")
+            .addIndexableNestedProperties("streetName")
+            .build())
+          .build();
+  }
+
+  @Override
+  public List> getDependencyDocumentClasses() throws AppSearchException {
+    List> classSet = new ArrayList>();
+    classSet.add(Address.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(Person document) throws AppSearchException {
+    GenericDocument.Builder builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String nameCopy = document.name;
+    if (nameCopy != null) {
+      builder.setPropertyString("name", nameCopy);
+    }
+    Address livesAtCopy = document.livesAt;
+    if (livesAtCopy != null) {
+      GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy);
+      builder.setPropertyDocument("livesAt", livesAtConv);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public Person fromGenericDocument(GenericDocument genericDoc,
+      Map> documentClassMap) throws AppSearchException {
+    String namespaceConv = genericDoc.getNamespace();
+    String idConv = genericDoc.getId();
+    String[] nameCopy = genericDoc.getPropertyStringArray("name");
+    String nameConv = null;
+    if (nameCopy != null && nameCopy.length != 0) {
+      nameConv = nameCopy[0];
+    }
+    GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
+    Address livesAtConv = null;
+    if (livesAtCopy != null) {
+      livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+    }
+    Person document = new Person();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.name = nameConv;
+    document.livesAt = livesAtConv;
+    return document;
+  }
+}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListTopLevelInheritTrue.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListTopLevelInheritTrue.JAVA
new file mode 100644
index 0000000..821dcc4
--- /dev/null
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListTopLevelInheritTrue.JAVA
@@ -0,0 +1,122 @@
+package com.example.appsearch;
+
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.DocumentClassFactory;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import java.lang.Class;
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.processing.Generated;
+
+@Generated("androidx.appsearch.compiler.AppSearchCompiler")
+public final class $$__AppSearch__ArtistEmployee implements DocumentClassFactory {
+  public static final String SCHEMA_NAME = "ArtistEmployee";
+
+  @Override
+  public String getSchemaName() {
+    return SCHEMA_NAME;
+  }
+
+  @Override
+  public AppSearchSchema getSchema() throws AppSearchException {
+    return new AppSearchSchema.Builder(SCHEMA_NAME)
+          .addParentType($$__AppSearch__Artist.SCHEMA_NAME)
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME)
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setShouldIndexNestedProperties(false)
+            .addIndexableNestedProperties("streetNumber")
+            .addIndexableNestedProperties("state")
+            .addIndexableNestedProperties("streetName")
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("worksAt")
+            .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+            .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
+            .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
+            .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE)
+            .build())
+          .build();
+  }
+
+  @Override
+  public List> getDependencyDocumentClasses() throws AppSearchException {
+    List> classSet = new ArrayList>();
+    classSet.add(Artist.class);
+    classSet.add(Address.class);
+    return classSet;
+  }
+
+  @Override
+  public GenericDocument toGenericDocument(ArtistEmployee document) throws AppSearchException {
+    GenericDocument.Builder builder =
+        new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME);
+    String nameCopy = document.name;
+    if (nameCopy != null) {
+      builder.setPropertyString("name", nameCopy);
+    }
+    Address livesAtCopy = document.livesAt;
+    if (livesAtCopy != null) {
+      GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy);
+      builder.setPropertyDocument("livesAt", livesAtConv);
+    }
+    String mostFamousWorkCopy = document.mostFamousWork;
+    if (mostFamousWorkCopy != null) {
+      builder.setPropertyString("mostFamousWork", mostFamousWorkCopy);
+    }
+    String worksAtCopy = document.worksAt;
+    if (worksAtCopy != null) {
+      builder.setPropertyString("worksAt", worksAtCopy);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public ArtistEmployee fromGenericDocument(GenericDocument genericDoc,
+      Map> documentClassMap) throws AppSearchException {
+    String namespaceConv = genericDoc.getNamespace();
+    String idConv = genericDoc.getId();
+    String[] nameCopy = genericDoc.getPropertyStringArray("name");
+    String nameConv = null;
+    if (nameCopy != null && nameCopy.length != 0) {
+      nameConv = nameCopy[0];
+    }
+    GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt");
+    Address livesAtConv = null;
+    if (livesAtCopy != null) {
+      livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap);
+    }
+    String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork");
+    String mostFamousWorkConv = null;
+    if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) {
+      mostFamousWorkConv = mostFamousWorkCopy[0];
+    }
+    String[] worksAtCopy = genericDoc.getPropertyStringArray("worksAt");
+    String worksAtConv = null;
+    if (worksAtCopy != null && worksAtCopy.length != 0) {
+      worksAtConv = worksAtCopy[0];
+    }
+    ArtistEmployee document = new ArtistEmployee();
+    document.namespace = namespaceConv;
+    document.id = idConv;
+    document.name = nameConv;
+    document.livesAt = livesAtConv;
+    document.mostFamousWork = mostFamousWorkConv;
+    document.worksAt = worksAtConv;
+    return document;
+  }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
index fe8a394..2f2b284 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
@@ -262,7 +262,7 @@
                         logger.warn(
                             """
                             A baseline profile was generated for the variant `${variantName.get()}`:
-                            $absolutePath
+                            file:///$absolutePath
                         """.trimIndent()
                         )
                     }
@@ -312,7 +312,7 @@
                         logger.warn(
                             """
                             A startup profile was generated for the variant `${variantName.get()}`:
-                            $absolutePath
+                            file:///$absolutePath
                         """.trimIndent()
                         )
                     }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
index b7686bc..aa4cf94 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
@@ -111,7 +111,7 @@
         gradleRunner.build("generateBaselineProfile") {
             val notFound = it.lines().requireInOrder(
                 "A baseline profile was generated for the variant `release`:",
-                baselineProfileFile("main").canonicalPath
+                "file:///${baselineProfileFile("main").canonicalPath}"
             )
             assertThat(notFound).isEmpty()
         }
@@ -154,9 +154,9 @@
         gradleRunner.build("generateBaselineProfile") {
             val notFound = it.lines().requireInOrder(
                 "A baseline profile was generated for the variant `release`:",
-                baselineProfileFile("release").canonicalPath,
+                "file:///${baselineProfileFile("release").canonicalPath}",
                 "A startup profile was generated for the variant `release`:",
-                startupProfileFile("release").canonicalPath
+                "file:///${startupProfileFile("release").canonicalPath}"
             )
             assertThat(notFound).isEmpty()
         }
@@ -237,9 +237,9 @@
 
                 val notFound = it.lines().requireInOrder(
                     "A baseline profile was generated for the variant `$variantName`:",
-                    baselineProfileFile(variantName).canonicalPath,
+                    "file:///${baselineProfileFile(variantName).canonicalPath}",
                     "A startup profile was generated for the variant `$variantName`:",
-                    startupProfileFile(variantName).canonicalPath
+                    "file:///${startupProfileFile(variantName).canonicalPath}"
                 )
 
                 assertWithMessage(
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
index 87e0497..9aec5bf 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
@@ -749,6 +749,42 @@
             }
         """,
     )
+
+    @Test
+    fun testMemoizationWStableCapture() = verifyGoldenComposeIrTransform(
+        source = """
+            import androidx.compose.runtime.*
+
+            @Composable fun Test(param: String, unstable: List<*>) {
+                Wrapper {
+                    println(param)
+                }
+            }
+        """,
+        extra = """
+                import androidx.compose.runtime.*
+
+                @Composable fun Wrapper(block: () -> Unit) {}
+            """,
+    )
+
+    @Test
+    fun testMemoizationWUnstableCapture() = verifyGoldenComposeIrTransform(
+        source = """
+            import androidx.compose.runtime.*
+
+            @Composable fun Test(param: String, unstable: List<*>) {
+                Wrapper {
+                    println(unstable)
+                }
+            }
+        """,
+        extra = """
+                import androidx.compose.runtime.*
+
+                @Composable fun Wrapper(block: () -> Unit) {}
+            """,
+    )
 }
 
 class RememberIntrinsicTransformTestsStrongSkipping(
@@ -795,4 +831,17 @@
                 @Composable fun Wrapper(block: () -> Unit) {}
             """,
     )
+
+    @Test
+    fun testRememberWithUnstableParam() = verifyGoldenComposeIrTransform(
+        source = """
+            import androidx.compose.runtime.*
+
+            @Composable fun Test(param: String, unstable: List<*>) {
+                remember(unstable) {
+                    unstable[0]
+                }
+            }
+        """,
+    )
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = false\135.txt"
new file mode 100644
index 0000000..f4fb2cc
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = false\135.txt"
@@ -0,0 +1,49 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.*
+
+@Composable fun Test(param: String, unstable: List<*>) {
+    Wrapper {
+        println(param)
+    }
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) {
+  %composer = %composer.startRestartGroup(<>)
+  sourceInformation(%composer, "C(Test)<{>,:Test.kt")
+  val %dirty = %changed
+  if (%changed and 0b1110 == 0) {
+    %dirty = %dirty or if (%composer.changed(param)) 0b0100 else 0b0010
+  }
+  if (%dirty and 0b1011 != 0b0010 || !%composer.skipping) {
+    if (isTraceInProgress()) {
+      traceEventStart(<>, %dirty, -1, <>)
+    }
+    Wrapper({
+      %composer.startReplaceableGroup(<>)
+      sourceInformation(%composer, "CC(remember):Test.kt#9igjgp")
+      val tmp0_group = %composer.cache(%dirty and 0b1110 == 0b0100) {
+        {
+          println(param)
+        }
+      }
+      %composer.endReplaceableGroup()
+      tmp0_group
+    }, %composer, 0)
+    if (isTraceInProgress()) {
+      traceEventEnd()
+    }
+  } else {
+    %composer.skipToGroupEnd()
+  }
+  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+    Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001))
+  }
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = true\135.txt"
new file mode 100644
index 0000000..f4fb2cc
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = true\135.txt"
@@ -0,0 +1,49 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.*
+
+@Composable fun Test(param: String, unstable: List<*>) {
+    Wrapper {
+        println(param)
+    }
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) {
+  %composer = %composer.startRestartGroup(<>)
+  sourceInformation(%composer, "C(Test)<{>,:Test.kt")
+  val %dirty = %changed
+  if (%changed and 0b1110 == 0) {
+    %dirty = %dirty or if (%composer.changed(param)) 0b0100 else 0b0010
+  }
+  if (%dirty and 0b1011 != 0b0010 || !%composer.skipping) {
+    if (isTraceInProgress()) {
+      traceEventStart(<>, %dirty, -1, <>)
+    }
+    Wrapper({
+      %composer.startReplaceableGroup(<>)
+      sourceInformation(%composer, "CC(remember):Test.kt#9igjgp")
+      val tmp0_group = %composer.cache(%dirty and 0b1110 == 0b0100) {
+        {
+          println(param)
+        }
+      }
+      %composer.endReplaceableGroup()
+      tmp0_group
+    }, %composer, 0)
+    if (isTraceInProgress()) {
+      traceEventEnd()
+    }
+  } else {
+    %composer.skipToGroupEnd()
+  }
+  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+    Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001))
+  }
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = false\135.txt"
new file mode 100644
index 0000000..aa030ee
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = false\135.txt"
@@ -0,0 +1,33 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.*
+
+@Composable fun Test(param: String, unstable: List<*>) {
+    Wrapper {
+        println(unstable)
+    }
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) {
+  %composer = %composer.startRestartGroup(<>)
+  sourceInformation(%composer, "C(Test):Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  Wrapper({
+    println(unstable)
+  }, %composer, 0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+    Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001))
+  }
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = true\135.txt"
new file mode 100644
index 0000000..aa030ee
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = true\135.txt"
@@ -0,0 +1,33 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.*
+
+@Composable fun Test(param: String, unstable: List<*>) {
+    Wrapper {
+        println(unstable)
+    }
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) {
+  %composer = %composer.startRestartGroup(<>)
+  sourceInformation(%composer, "C(Test):Test.kt")
+  if (isTraceInProgress()) {
+    traceEventStart(<>, %changed, -1, <>)
+  }
+  Wrapper({
+    println(unstable)
+  }, %composer, 0)
+  if (isTraceInProgress()) {
+    traceEventEnd()
+  }
+  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+    Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001))
+  }
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = false\135.txt"
new file mode 100644
index 0000000..9cc440b
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = false\135.txt"
@@ -0,0 +1,45 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.*
+
+@Composable fun Test(param: String, unstable: List<*>) {
+    remember(unstable) {
+        unstable[0]
+    }
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) {
+  %composer = %composer.startRestartGroup(<>)
+  sourceInformation(%composer, "C(Test):Test.kt")
+  val %dirty = %changed
+  if (%changed and 0b00110000 == 0) {
+    %dirty = %dirty or if (%composer.changedInstance(unstable)) 0b00100000 else 0b00010000
+  }
+  if (%dirty and 0b00010001 != 0b00010000 || !%composer.skipping) {
+    if (isTraceInProgress()) {
+      traceEventStart(<>, %dirty, -1, <>)
+    }
+    %composer.startReplaceableGroup(<>)
+    sourceInformation(%composer, "CC(remember):Test.kt#9igjgp")
+    val tmp0_group = %composer.cache(%composer.changed(unstable)) {
+      unstable[0]
+    }
+    %composer.endReplaceableGroup()
+    tmp0_group
+    if (isTraceInProgress()) {
+      traceEventEnd()
+    }
+  } else {
+    %composer.skipToGroupEnd()
+  }
+  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+    Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001))
+  }
+}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = true\135.txt"
new file mode 100644
index 0000000..9cc440b
--- /dev/null
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = true\135.txt"
@@ -0,0 +1,45 @@
+//
+// Source
+// ------------------------------------------
+
+import androidx.compose.runtime.*
+
+@Composable fun Test(param: String, unstable: List<*>) {
+    remember(unstable) {
+        unstable[0]
+    }
+}
+
+//
+// Transformed IR
+// ------------------------------------------
+
+@Composable
+fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) {
+  %composer = %composer.startRestartGroup(<>)
+  sourceInformation(%composer, "C(Test):Test.kt")
+  val %dirty = %changed
+  if (%changed and 0b00110000 == 0) {
+    %dirty = %dirty or if (%composer.changedInstance(unstable)) 0b00100000 else 0b00010000
+  }
+  if (%dirty and 0b00010001 != 0b00010000 || !%composer.skipping) {
+    if (isTraceInProgress()) {
+      traceEventStart(<>, %dirty, -1, <>)
+    }
+    %composer.startReplaceableGroup(<>)
+    sourceInformation(%composer, "CC(remember):Test.kt#9igjgp")
+    val tmp0_group = %composer.cache(%composer.changed(unstable)) {
+      unstable[0]
+    }
+    %composer.endReplaceableGroup()
+    tmp0_group
+    if (isTraceInProgress()) {
+      traceEventEnd()
+    }
+  } else {
+    %composer.skipToGroupEnd()
+  }
+  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+    Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001))
+  }
+}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
index f7a2f24..117e9f7 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
@@ -1204,7 +1204,7 @@
         currentComposer: IrExpression,
         value: IrExpression,
         inferredStable: Boolean,
-        strongSkippingEnabled: Boolean
+        compareInstanceForUnstableValues: Boolean
     ): IrExpression {
         // compose has a unique opportunity to avoid inline class boxing for changed calls, since
         // we know that the only thing that we are detecting here is "changed or not", we can
@@ -1222,7 +1222,7 @@
         val primitiveDescriptor = type.toPrimitiveType()
             .let { changedPrimitiveFunctions[it] }
 
-        return if (!strongSkippingEnabled) {
+        return if (!compareInstanceForUnstableValues) {
             val descriptor = primitiveDescriptor
                 ?: if (type.isFunction()) changedInstanceFunction else changedFunction
             irMethodCall(currentComposer, descriptor).also {
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 c18ad5c..409e4ac 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
@@ -983,7 +983,7 @@
             }
         }
 
-        scope.applyIntrinsicRememberFixups { args, metas ->
+        scope.applyIntrinsicRememberFixups { isMemoizedLambda, args, metas ->
             // replace dirty with changed param in meta used for inference, as we are not
             // populating dirty
             if (!canSkipExecution) {
@@ -993,7 +993,7 @@
                     }
                 }
             }
-            irIntrinsicRememberInvalid(args, metas, ::irInferredChanged)
+            irIntrinsicRememberInvalid(isMemoizedLambda, args, metas, ::irInferredChanged)
         }
 
         if (canSkipExecution) {
@@ -1149,7 +1149,7 @@
             dirty
         } else changedParam
 
-        scope.applyIntrinsicRememberFixups { args, metas ->
+        scope.applyIntrinsicRememberFixups { isMemoizedLambda, args, metas ->
             // replace dirty with changed param in meta used for inference, as we are not
             // populating dirty
             if (!canSkipExecution) {
@@ -1159,7 +1159,7 @@
                     }
                 }
             }
-            irIntrinsicRememberInvalid(args, metas, ::irInferredChanged)
+            irIntrinsicRememberInvalid(isMemoizedLambda, args, metas, ::irInferredChanged)
         }
 
         val transformedBody = if (canSkipExecution) {
@@ -1636,13 +1636,13 @@
                 irCurrentComposer(),
                 irGet(param),
                 inferredStable = true,
-                strongSkippingEnabled = true
+                compareInstanceForUnstableValues = true
             ),
             elsePart = irChanged(
                 irCurrentComposer(),
                 irGet(param),
                 inferredStable = false,
-                strongSkippingEnabled = true
+                compareInstanceForUnstableValues = true
             )
         )
     } else {
@@ -2119,11 +2119,14 @@
         return irMethodCall(scope.irCurrentComposer(), endRestartGroupFunction)
     }
 
-    private fun irChanged(value: IrExpression): IrExpression = irChanged(
+    private fun irChanged(
+        value: IrExpression,
+        compareInstanceForUnstableValues: Boolean = strongSkippingEnabled
+    ): IrExpression = irChanged(
         irCurrentComposer(),
         value,
         inferredStable = false,
-        strongSkippingEnabled = strongSkippingEnabled
+        compareInstanceForUnstableValues = compareInstanceForUnstableValues
     )
 
     private fun irSkipToGroupEnd(startOffset: Int, endOffset: Int): IrExpression {
@@ -3033,18 +3036,25 @@
         }
         val usesDirty = inputArgMetas.any { it.maskParam is IrChangedBitMaskVariable }
 
+        val isMemoizedLambda = expression.origin == ComposeMemoizedLambdaOrigin
+
         // We can only rely on the $changed or $dirty if the flags are correctly updated in
         // the restart function or the result of replacing remember with cached will be
         // different.
         val metaMaskConsistent = updateChangedFlagsFunction != null
-        val changedFunction: (IrExpression, ParamMeta) -> IrExpression? =
+        val changedFunction: (Boolean, IrExpression, ParamMeta) -> IrExpression? =
             if (usesDirty || !metaMaskConsistent) {
-                { arg, _ -> irChanged(arg) }
+                { _, arg, _ -> irChanged(arg, compareInstanceForUnstableValues = isMemoizedLambda) }
             } else {
                 ::irInferredChanged
             }
 
-        val invalidExpr = irIntrinsicRememberInvalid(inputArgs, inputArgMetas, changedFunction)
+        val invalidExpr = irIntrinsicRememberInvalid(
+            isMemoizedLambda,
+            inputArgs,
+            inputArgMetas,
+            changedFunction
+        )
         val functionScope = currentFunctionScope
         val cacheCall = irCache(
             irCurrentComposer(),
@@ -3056,6 +3066,7 @@
         )
         if (usesDirty && metaMaskConsistent) {
             functionScope.recordIntrinsicRememberFixUp(
+                isMemoizedLambda,
                 inputArgs,
                 inputArgMetas,
                 cacheCall
@@ -3103,16 +3114,21 @@
     }
 
     private fun irIntrinsicRememberInvalid(
+        isMemoizedLambda: Boolean,
         args: List,
         metas: List,
-        changedExpr: (IrExpression, ParamMeta) -> IrExpression?
+        changedExpr: (Boolean, IrExpression, ParamMeta) -> IrExpression?
     ): IrExpression =
         args
-            .mapIndexedNotNull { i, arg -> changedExpr(arg, metas[i]) }
+            .mapIndexedNotNull { i, arg -> changedExpr(isMemoizedLambda, arg, metas[i]) }
             .reduceOrNull { acc, changed -> irBooleanOr(acc, changed) }
             ?: irConst(false)
 
-    private fun irInferredChanged(arg: IrExpression, meta: ParamMeta): IrExpression? {
+    private fun irInferredChanged(
+        isMemoizedLambda: Boolean,
+        arg: IrExpression,
+        meta: ParamMeta
+    ): IrExpression? {
         val param = meta.maskParam
         return when {
             meta.isStatic -> null
@@ -3144,7 +3160,7 @@
                 val stableBits = param.irSlotAnd(meta.maskSlot, StabilityBits.UNSTABLE.bits)
                 val maskIsUnstableAndChanged = irAndAnd(
                     irNotEqual(stableBits, irConst(0)),
-                    irChanged(arg)
+                    irChanged(arg, compareInstanceForUnstableValues = isMemoizedLambda)
                 )
                 irOrOr(
                     maskIsStableAndDifferent,
@@ -3173,7 +3189,7 @@
                 irOrOr(
                     irAndAnd(
                         maskIsUnstableOrUncertain,
-                        irChanged(arg)
+                        irChanged(arg, compareInstanceForUnstableValues = isMemoizedLambda)
                     ),
                     irEqual(
                         param.irIsolateBitsAtSlot(meta.maskSlot, includeStableBit = false),
@@ -3181,7 +3197,7 @@
                     )
                 )
             }
-            else -> irChanged(arg)
+            else -> irChanged(arg, compareInstanceForUnstableValues = isMemoizedLambda)
         }
     }
 
@@ -3936,6 +3952,7 @@
             }
 
             private class IntrinsicRememberFixup(
+                val isMemoizedLambda: Boolean,
                 val args: List,
                 val metas: List,
                 val call: IrCall
@@ -3943,13 +3960,16 @@
             private val intrinsicRememberFixups = mutableListOf()
 
             fun recordIntrinsicRememberFixUp(
+                isMemoizedLambda: Boolean,
                 args: List,
                 metas: List,
                 call: IrCall
             ) {
                 val dirty = metas.find { it.maskParam is IrChangedBitMaskVariable }
                 if (dirty?.maskParam == this.dirty) {
-                    intrinsicRememberFixups.add(IntrinsicRememberFixup(args, metas, call))
+                    intrinsicRememberFixups.add(
+                        IntrinsicRememberFixup(isMemoizedLambda, args, metas, call)
+                    )
                 } else {
                     // capturing dirty is only allowed from inline function context, which doesn't
                     // have dirty params.
@@ -3957,15 +3977,19 @@
                     // means that we should apply the fixup higher in the tree.
                     var scope = parent
                     while (scope !is FunctionScope) scope = scope!!.parent
-                    scope.recordIntrinsicRememberFixUp(args, metas, call)
+                    scope.recordIntrinsicRememberFixUp(isMemoizedLambda, args, metas, call)
                 }
             }
 
             fun applyIntrinsicRememberFixups(
-                invalidExpr: (List, List) -> IrExpression
+                invalidExpr: (
+                    isMemoizedLambda: Boolean,
+                    List,
+                    List
+                ) -> IrExpression
             ) {
                 intrinsicRememberFixups.forEach {
-                    val invalid = invalidExpr(it.args, it.metas)
+                    val invalid = invalidExpr(it.isMemoizedLambda, it.args, it.metas)
                     // $composer.cache(invalid, calc)
                     it.call.putValueArgument(0, invalid)
                 }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index c727084..ff49298 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -996,7 +996,8 @@
 
         return irBuilder.irCall(
             callee = rememberFunctionSymbol,
-            type = expression.type
+            type = expression.type,
+            origin = ComposeMemoizedLambdaOrigin
         ).apply {
             // The result type type parameter is first, followed by the argument types
             putTypeArgument(0, expression.type)
@@ -1042,7 +1043,7 @@
         irCurrentComposer(),
         value,
         inferredStable = false,
-        strongSkippingEnabled = strongSkippingModeEnabled
+        compareInstanceForUnstableValues = strongSkippingModeEnabled
     )
 
     private fun IrValueDeclaration.isVar(): Boolean =
@@ -1135,3 +1136,5 @@
 
 // This must match the highest value of FunctionXX which is current Function22
 private const val MAX_RESTART_ARGUMENT_COUNT = 22
+
+internal object ComposeMemoizedLambdaOrigin : IrStatementOrigin
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt
index c7eae7a..f83cf84 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt
@@ -24,7 +24,6 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.testutils.assertAgainstGolden
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.captureToImage
@@ -41,7 +40,6 @@
 import androidx.compose.ui.text.style.LineHeightStyle
 import androidx.compose.ui.text.style.LineHeightStyle.Alignment
 import androidx.compose.ui.text.style.LineHeightStyle.Trim
-import androidx.compose.ui.unit.DisableNonLinearFontScalingInCompose
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.em
 import androidx.compose.ui.unit.sp
@@ -58,7 +56,6 @@
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
-@OptIn(ExperimentalComposeUiApi::class)
 class FontScalingScreenshotTest {
     @get:Rule
     val rule = createAndroidComposeRule()
@@ -71,7 +68,6 @@
     @After
     fun teardown() {
         AndroidFontScaleHelper.resetSystemFontScale(rule.activityRule.scenario)
-        DisableNonLinearFontScalingInCompose = false
     }
 
     @Test
@@ -184,40 +180,6 @@
             .assertAgainstGolden(screenshotRule, "fontScaling2x_drawText")
     }
 
-    @Test
-    fun fontScaling2x_DisableNonLinearFontScalingFlag_lineHeightDoubleSp() {
-        DisableNonLinearFontScalingInCompose = true
-        AndroidFontScaleHelper.setSystemFontScale(2f, rule.activityRule.scenario)
-        rule.waitForIdle()
-
-        rule.setContent {
-            TestLayout(lineHeight = 28.sp)
-        }
-        rule.onNodeWithTag(containerTag)
-            .captureToImage()
-            .assertAgainstGolden(
-                screenshotRule,
-                "fontScaling2x_DisableNonLinearFontScalingFlag_lineHeightDoubleSp"
-            )
-    }
-
-    @Test
-    fun fontScaling2x_DisableNonLinearFontScalingFlag_drawText() {
-        DisableNonLinearFontScalingInCompose = true
-        AndroidFontScaleHelper.setSystemFontScale(2f, rule.activityRule.scenario)
-        rule.waitForIdle()
-
-        rule.setContent {
-            TestDrawTextLayout()
-        }
-        rule.onNodeWithTag(containerTag)
-            .captureToImage()
-            .assertAgainstGolden(
-                screenshotRule,
-                "fontScaling2x_DisableNonLinearFontScalingFlag_drawText"
-            )
-    }
-
     @Composable
     private fun TestLayout(
         lineHeight: TextUnit,
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
index 3c26f67..b49c3b3 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
@@ -105,6 +105,18 @@
     examples = CardExamples
 )
 
+private val Carousel = Component(
+    id = nextId(),
+    name = "Carousel",
+    description = "Carousels are stylized versions of lists that provide a unique viewing and " +
+        "behavior that suit large imagery and other visually rich content.",
+    // No carousel icon
+    guidelinesUrl = "$StyleGuidelinesUrl/carousel",
+    docsUrl = "$PackageSummaryUrl#carousel",
+    sourceUrl = "$Material3SourceUrl/Carousel.kt",
+    examples = CarouselExamples
+)
+
 private val Checkboxes = Component(
     id = nextId(),
     name = "Checkboxes",
@@ -414,6 +426,7 @@
     BottomSheets,
     Buttons,
     Card,
+    Carousel,
     Checkboxes,
     Chips,
     DatePickers,
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index 63dc67e..08cafdb 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -35,6 +35,7 @@
 import androidx.compose.material3.samples.ButtonSample
 import androidx.compose.material3.samples.ButtonWithIconSample
 import androidx.compose.material3.samples.CardSample
+import androidx.compose.material3.samples.CarouselSample
 import androidx.compose.material3.samples.CheckboxSample
 import androidx.compose.material3.samples.CheckboxWithTextSample
 import androidx.compose.material3.samples.ChipGroupReflowSample
@@ -286,6 +287,18 @@
     }
 )
 
+private const val CarouselExampleDescription = "Carousel examples"
+private const val CarouselExampleSourceUrl = "$SampleSourceUrl/CarouselSamples.kt"
+val CarouselExamples = listOf(
+    Example(
+        name = ::CarouselSample.name,
+        description = CarouselExampleDescription,
+        sourceUrl = CarouselExampleSourceUrl
+    ) {
+        CarouselSample()
+    }
+)
+
 private const val CheckboxesExampleDescription = "Checkboxes examples"
 private const val CheckboxesExampleSourceUrl = "$SampleSourceUrl/CheckboxSamples.kt"
 val CheckboxesExamples = listOf(
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/CarouselSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/CarouselSamples.kt
new file mode 100644
index 0000000..a55a9bd
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/CarouselSamples.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.samples
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.Card
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@Preview
+@Composable
+fun CarouselSample() {
+    data class CarouselItem(
+        val id: Int,
+        @DrawableRes val imageResId: Int,
+        @StringRes val contentDescriptionResId: Int
+    )
+
+    val Items = listOf(
+        CarouselItem(0, R.drawable.carousel_image_1, R.string.carousel_image_1_description),
+        CarouselItem(1, R.drawable.carousel_image_2, R.string.carousel_image_2_description),
+        CarouselItem(2, R.drawable.carousel_image_3, R.string.carousel_image_3_description),
+        CarouselItem(3, R.drawable.carousel_image_4, R.string.carousel_image_4_description),
+        CarouselItem(4, R.drawable.carousel_image_5, R.string.carousel_image_5_description),
+    )
+
+    LazyRow(
+        modifier = Modifier.fillMaxWidth(),
+        state = rememberLazyListState()
+    ) {
+        itemsIndexed(Items) { _, item ->
+            Card(
+                modifier = Modifier
+                    .width(350.dp)
+                    .height(200.dp),
+            ) {
+                Image(
+                    painter = painterResource(id = item.imageResId),
+                    contentDescription = stringResource(item.contentDescriptionResId),
+                    modifier = Modifier.fillMaxSize(),
+                    contentScale = ContentScale.Crop
+                )
+            }
+        }
+    }
+}
diff --git a/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_1.jpg b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_1.jpg
new file mode 100644
index 0000000..b02612e
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_1.jpg
Binary files differ
diff --git a/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_2.jpg b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_2.jpg
new file mode 100644
index 0000000..73162bb
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_2.jpg
Binary files differ
diff --git a/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_3.jpg b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_3.jpg
new file mode 100644
index 0000000..d31f632
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_3.jpg
Binary files differ
diff --git a/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_4.jpg b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_4.jpg
new file mode 100644
index 0000000..8362062
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_4.jpg
Binary files differ
diff --git a/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_5.jpg b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_5.jpg
new file mode 100644
index 0000000..f5eb364
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_5.jpg
Binary files differ
diff --git a/compose/material3/material3/samples/src/main/res/values/strings.xml b/compose/material3/material3/samples/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b52b1ea
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@
+
+
+
+
+    A racecar
+    Dotonburi, Osaka
+    The Grand Canyon
+    A rock structure with its reflection mirrored over the sea
+    A car on a long stretch of desert road
+
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
index 0d2dad7..bc6bb20 100644
--- a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
@@ -61,8 +61,8 @@
     "Desprega o panel inferior"
     "Cadro de información"
     "Mostrar o cadro de información"
-    "PM"
-    "AM"
+    "p.m."
+    "a.m."
     "Selecciona a.m. ou p.m."
     "Selecciona a hora"
     "Selecciona os minutos"
diff --git a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
index 9810ca8..91ff286 100644
--- a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
@@ -61,8 +61,8 @@
     "Extinde foaia din partea de jos"
     "Balon explicativ"
     "Afișează balonul explicativ"
-    "PM"
-    "AM"
+    "p.m."
+    "a.m."
     "Selectează AM sau PM"
     "Selectează ora"
     "Selectează minutele"
diff --git a/compose/ui/ui-unit/api/current.ignore b/compose/ui/ui-unit/api/current.ignore
new file mode 100644
index 0000000..0e3bda5
--- /dev/null
+++ b/compose/ui/ui-unit/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedClass: androidx.compose.ui.unit.FontScalingKt:
+    Removed class androidx.compose.ui.unit.FontScalingKt
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt
index eba7f2c..c7a1cfb 100644
--- a/compose/ui/ui-unit/api/current.txt
+++ b/compose/ui/ui-unit/api/current.txt
@@ -199,12 +199,6 @@
     property public abstract float fontScale;
   }
 
-  public final class FontScalingKt {
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static boolean getDisableNonLinearFontScalingInCompose();
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void setDisableNonLinearFontScalingInCompose(boolean);
-    property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final boolean DisableNonLinearFontScalingInCompose;
-  }
-
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntOffset {
     method @androidx.compose.runtime.Stable public operator int component1();
     method @androidx.compose.runtime.Stable public operator int component2();
diff --git a/compose/ui/ui-unit/api/restricted_current.ignore b/compose/ui/ui-unit/api/restricted_current.ignore
new file mode 100644
index 0000000..0e3bda5
--- /dev/null
+++ b/compose/ui/ui-unit/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedClass: androidx.compose.ui.unit.FontScalingKt:
+    Removed class androidx.compose.ui.unit.FontScalingKt
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt
index e9b932e..4065f7f 100644
--- a/compose/ui/ui-unit/api/restricted_current.txt
+++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -199,12 +199,6 @@
     property public abstract float fontScale;
   }
 
-  public final class FontScalingKt {
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static boolean getDisableNonLinearFontScalingInCompose();
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void setDisableNonLinearFontScalingInCompose(boolean);
-    property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final boolean DisableNonLinearFontScalingInCompose;
-  }
-
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntOffset {
     method @androidx.compose.runtime.Stable public operator int component1();
     method @androidx.compose.runtime.Stable public operator int component2();
diff --git a/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/AndroidDensity.android.kt b/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/AndroidDensity.android.kt
index c52998f..37e1c09 100644
--- a/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/AndroidDensity.android.kt
+++ b/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/AndroidDensity.android.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.unit
 
 import android.content.Context
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.unit.fontscaling.FontScaleConverter
 import androidx.compose.ui.unit.fontscaling.FontScaleConverterFactory
 
@@ -26,16 +25,12 @@
  *
  * @param context density values will be extracted from this [Context]
  */
-@OptIn(ExperimentalComposeUiApi::class)
 fun Density(context: Context): Density {
     val fontScale = context.resources.configuration.fontScale
-    val converter = if (DisableNonLinearFontScalingInCompose) LinearFontScaleConverter(fontScale)
-        else FontScaleConverterFactory.forScale(fontScale) ?: LinearFontScaleConverter(fontScale)
-
     return DensityWithConverter(
         context.resources.displayMetrics.density,
         fontScale,
-        converter
+        FontScaleConverterFactory.forScale(fontScale) ?: LinearFontScaleConverter(fontScale)
     )
 }
 
diff --git a/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/FontScaling.android.kt b/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/FontScaling.android.kt
index e9f3565..e18c850 100644
--- a/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/FontScaling.android.kt
+++ b/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/FontScaling.android.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.unit.fontscaling.FontScaleConverterFactory
 import androidx.compose.ui.unit.internal.JvmDefaultWithCompatibility
 
@@ -31,7 +30,6 @@
  */
 @Immutable
 @JvmDefaultWithCompatibility
-@OptIn(ExperimentalComposeUiApi::class)
 actual interface FontScaling {
     /**
      * Current user preference for the scaling factor for fonts.
@@ -44,8 +42,7 @@
      */
     @Stable
     actual fun Dp.toSp(): TextUnit {
-        if (!FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale) ||
-            DisableNonLinearFontScalingInCompose) {
+        if (!FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale)) {
             return (value / fontScale).sp
         }
 
@@ -60,8 +57,7 @@
     @Stable
     actual fun TextUnit.toDp(): Dp {
         check(type == TextUnitType.Sp) { "Only Sp can convert to Px" }
-        if (!FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale) ||
-            DisableNonLinearFontScalingInCompose) {
+        if (!FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale)) {
             return Dp(value * fontScale)
         }
 
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/FontScaling.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/FontScaling.kt
index 65d23a8..68c3238 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/FontScaling.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/FontScaling.kt
@@ -19,30 +19,9 @@
 import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.unit.internal.JvmDefaultWithCompatibility
 
 /**
- * Flag indicating if [Density] will use non-linear font scaling for Compose.
- *
- * Set this flag to true to keep the old linear font scaling behavior. Note that this only affects
- * Compose. Views always use non-linear font scaling in Android 14 and later.
- *
- * This flag will be removed in Compose 1.6.0-beta01. If you encounter any issues with the
- * new behavior, please file an issue at: issuetracker.google.com/issues/new?component=779818
- */
-// TODO(b/300538470): Remove flag before beta
-@Suppress("GetterSetterNames", "OPT_IN_MARKER_ON_WRONG_TARGET")
-@get:Suppress("GetterSetterNames")
-@set:ExperimentalComposeUiApi
-@get:ExperimentalComposeUiApi
-@ExperimentalComposeUiApi
-var DisableNonLinearFontScalingInCompose by mutableStateOf(false)
-
-/**
  * Converts [TextUnit] to [Dp] and vice-versa.
  *
  * If you are implementing this interface yourself on Android, please check the docs for important
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
index 6601791..ba339c6 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
@@ -19,6 +19,7 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -198,6 +199,51 @@
         assertTrue(onRouteEnabledLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
+    @Test
+    @MediumTest
+    public void addUserRouteFromMr1_isSystemRoute_returnsFalse() throws Exception {
+        getInstrumentation()
+                .runOnMainSync(
+                        () -> {
+                            android.media.MediaRouter mediaRouter1 =
+                                    (android.media.MediaRouter)
+                                            mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+
+                            android.media.MediaRouter.RouteCategory sampleRouteCategory =
+                                    mediaRouter1.createRouteCategory(
+                                            "SAMPLE_ROUTE_CATEGORY", /* isGroupable= */ false);
+
+                            android.media.MediaRouter.UserRouteInfo sampleUserRoute =
+                                    mediaRouter1.createUserRoute(sampleRouteCategory);
+                            sampleUserRoute.setName("SAMPLE_USER_ROUTE");
+
+                            mediaRouter1.addUserRoute(sampleUserRoute);
+
+                            for (RouteInfo routeInfo : mRouter.getRoutes()) {
+                                // We are checking for this route using getRoutes rather than
+                                // through the onRouteAdded callback because of b/312700919
+                                if (routeInfo.getName().equals("SAMPLE_USER_ROUTE")) {
+                                    assertFalse(routeInfo.isSystemRoute());
+                                }
+                            }
+                        });
+
+    }
+
+    @Test
+    @MediumTest
+    public void defaultAndBluetoothRoutes_isSystemRoute_returnsTrue() {
+        getInstrumentation()
+                .runOnMainSync(
+                        () -> {
+                            for (RouteInfo routeInfo : mRouter.getRoutes()) {
+                                if (routeInfo.isDefaultOrBluetooth()) {
+                                    assertTrue(routeInfo.isSystemRoute());
+                                }
+                            }
+                        });
+    }
+
     @SmallTest
     @Test
     public void setRouteVolume_onStaticNonGroupRoute() {
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
index 2d3bdfb..7cf8cff 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
@@ -516,7 +516,7 @@
             builder.setVolume(record.mRoute.getVolume());
             builder.setVolumeMax(record.mRoute.getVolumeMax());
             builder.setVolumeHandling(record.mRoute.getVolumeHandling());
-            builder.setIsSystemRoute(true);
+            builder.setIsSystemRoute((supportedTypes & ROUTE_TYPE_USER) == 0);
 
             if (!record.mRoute.isEnabled()) {
                 builder.setEnabled(false);