Snap for 11268588 from a8ee5e92163ac6820dec08c906c9906a9b977d4e to mainline-art-release

Change-Id: I9ff573ba5daa5f086c5f63aaae58cc5788bb8665
diff --git a/framework/java/android/app/appsearch/aidl/AppSearchAttributionSource.java b/framework/java/android/app/appsearch/aidl/AppSearchAttributionSource.java
index 5771e5f..c2ecb9d 100644
--- a/framework/java/android/app/appsearch/aidl/AppSearchAttributionSource.java
+++ b/framework/java/android/app/appsearch/aidl/AppSearchAttributionSource.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresApi;
+import android.app.appsearch.safeparcel.AbstractSafeParcelable;
+import android.app.appsearch.safeparcel.SafeParcelable;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.os.Binder;
@@ -44,60 +46,78 @@
  *
  * @hide
  */
-// TODO(b/275629842): Update the class to use SafeParcelable instead of Parcelable.
-public final class AppSearchAttributionSource implements Parcelable {
+@SafeParcelable.Class(creator = "AppSearchAttributionSourceCreator")
+public final class AppSearchAttributionSource extends AbstractSafeParcelable {
+    @NonNull
+    public static final AppSearchAttributionSourceCreator CREATOR =
+        new AppSearchAttributionSourceCreator();
 
+    @NonNull
     private final Compat mCompat;
-    @Nullable private final String mCallingPackageName;
+
+    @Nullable
+    @Field(id = 1, getter = "getAttributionSource")
+    private final AttributionSource mAttributionSource;
+    @Nullable
+    @Field(id = 2, getter = "getPackageName")
+    private final String mCallingPackageName;
+    @Field(id = 3, getter = "getUid")
     private final int mCallingUid;
 
     /**
+     * Constructs an instance of AppSearchAttributionSource for AbstractSafeParcelable.
+     * @param attributionSource The attribution source that is accessing permission
+     *      protected data.
+     * @param callingPackageName The package that is accessing the permission protected data.
+     * @param callingUid The UID that is accessing the permission protected data.
+     */
+    @Constructor
+    AppSearchAttributionSource(
+        @Param(id = 1) @Nullable AttributionSource attributionSource,
+        @Param(id = 2) @Nullable String callingPackageName,
+        @Param(id = 3) int callingUid) {
+        mAttributionSource = attributionSource;
+        mCallingPackageName = callingPackageName;
+        mCallingUid = callingUid;
+        if (VERSION.SDK_INT >= Build.VERSION_CODES.S && mAttributionSource != null) {
+            mCompat = new Api31Impl(mAttributionSource);
+        } else {
+            mCompat = new Api19Impl(mCallingPackageName, mCallingUid);
+        }
+    }
+
+    /**
      * Constructs an instance of AppSearchAttributionSource.
      * @param compat The compat version that provides AttributionSource implementation on
      *      lower API levels.
      */
     private AppSearchAttributionSource(@NonNull Compat compat) {
         mCompat = Objects.requireNonNull(compat);
+        mAttributionSource = mCompat.getAttributionSource();
         mCallingPackageName = mCompat.getPackageName();
         mCallingUid = mCompat.getUid();
     }
 
+    /**
+     * Constructs an instance of AppSearchAttributionSource for testing.
+     * @param callingPackageName The package that is accessing the permission protected data.
+     * @param callingUid The UID that is accessing the permission protected data.
+     */
     @VisibleForTesting
     public AppSearchAttributionSource(@Nullable String callingPackageName, int callingUid) {
         mCallingPackageName = callingPackageName;
         mCallingUid = callingUid;
 
         if (VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            AttributionSource attributionSource =
-                new AttributionSource.Builder(mCallingUid)
-                        .setPackageName(mCallingPackageName).build();
-            mCompat = new Api31Impl(attributionSource);
+             mAttributionSource = new AttributionSource.Builder(mCallingUid)
+                    .setPackageName(mCallingPackageName).build();
+            mCompat = new Api31Impl(mAttributionSource);
         } else {
+            mAttributionSource = null;
             mCompat = new Api19Impl(mCallingPackageName, mCallingUid);
         }
     }
 
-    private AppSearchAttributionSource(@NonNull Parcel in) {
-        if (VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            // Deserializing attributionSource calls enforceCallingUidAndPid similar to
-            // AttributionSource.
-            AttributionSource attributionSource =
-                in.readParcelable(AttributionSource.class.getClassLoader());
-            mCompat = new Api31Impl(attributionSource);
-            mCallingPackageName = mCompat.getPackageName();
-            mCallingUid = mCompat.getUid();
-        } else {
-            mCallingPackageName = in.readString();
-            mCallingUid = in.readInt();
-            Api19Impl impl = new Api19Impl(mCallingPackageName, mCallingUid);
-            // Enforce calling pid and uid must be called here on R and below similar to how
-            // AttributionSource is implemented.
-            impl.enforceCallingUid();
-            impl.enforceCallingPid();
-            mCompat = impl;
-        }
-    }
-
     /**
      * Provides a backward-compatible wrapper for AttributionSource.
      *
@@ -165,7 +185,7 @@
     public int hashCode() {
         if (VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             AttributionSource attributionSource = Objects.requireNonNull(
-                    mCompat.getAttributionSource());
+                mCompat.getAttributionSource());
             return attributionSource.hashCode();
         }
 
@@ -181,44 +201,21 @@
         AppSearchAttributionSource that = (AppSearchAttributionSource) o;
         if (VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             AttributionSource thisAttributionSource = Objects.requireNonNull(
-                    mCompat.getAttributionSource());
+                mCompat.getAttributionSource());
             AttributionSource thatAttributionSource = Objects.requireNonNull(
-                    that.getAttributionSource());
+                that.getAttributionSource());
             return thisAttributionSource.equals(thatAttributionSource);
         }
 
         return (Objects.equals(mCompat.getPackageName(), that.getPackageName())
-                && (mCompat.getUid() == that.getUid()));
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
+            && (mCompat.getUid() == that.getUid()));
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        if (VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            dest.writeParcelable(mCompat.getAttributionSource(), flags);
-        } else {
-            dest.writeString(mCompat.getPackageName());
-            dest.writeInt(mCompat.getUid());
-        }
+        AppSearchAttributionSourceCreator.writeToParcel(this, dest, flags);
     }
 
-    public static final Creator<AppSearchAttributionSource> CREATOR =
-        new Creator<>() {
-            @Override
-            public AppSearchAttributionSource createFromParcel(Parcel in) {
-                return new AppSearchAttributionSource(in);
-            }
-
-            @Override
-            public AppSearchAttributionSource[] newArray(int size) {
-                return new AppSearchAttributionSource[size];
-            }
-        };
-
     /** Compat class for AttributionSource to provide implementation for lower API levels. */
     private interface Compat {
         /** The package that is accessing the permission protected data. */
diff --git a/framework/java/external/android/app/appsearch/AppSearchSchema.java b/framework/java/external/android/app/appsearch/AppSearchSchema.java
index 02152d7..a0eea64 100644
--- a/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -36,6 +36,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -181,7 +182,7 @@
     public static final class Builder {
         private final String mSchemaType;
         private ArrayList<Bundle> mPropertyBundles = new ArrayList<>();
-        private ArraySet<String> mParentTypes = new ArraySet<>();
+        private LinkedHashSet<String> mParentTypes = new LinkedHashSet<>();
         private final Set<String> mPropertyNames = new ArraySet<>();
         private boolean mBuilt = false;
 
@@ -289,7 +290,7 @@
         private void resetIfBuilt() {
             if (mBuilt) {
                 mPropertyBundles = new ArrayList<>(mPropertyBundles);
-                mParentTypes = new ArraySet<>(mParentTypes);
+                mParentTypes = new LinkedHashSet<>(mParentTypes);
                 mBuilt = false;
             }
         }
@@ -1342,7 +1343,7 @@
              *
              * @throws IllegalArgumentException if the provided PropertyConfig sets {@link
              *     #shouldIndexNestedProperties()} to true and has one or more properties defined
-             *     for {@link #getIndexableNestedProperties()}.
+             *     using {@link #addIndexableNestedProperties(Collection)}.
              */
             @NonNull
             public DocumentPropertyConfig build() {
diff --git a/framework/java/external/android/app/appsearch/GenericDocument.java b/framework/java/external/android/app/appsearch/GenericDocument.java
index 9766748..a977c68 100644
--- a/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -887,10 +887,13 @@
      *
      * <p>The returned builder is a deep copy whose data is separate from this document.
      *
+     * @deprecated This API is not compliant with API guidelines. Use {@link
+     *     Builder#Builder(GenericDocument)} instead.
      * @hide
      */
     // TODO(b/171882200): Expose this API in Android T
     @NonNull
+    @Deprecated
     public GenericDocument.Builder<GenericDocument.Builder<?>> toBuilder() {
         Bundle clonedBundle = BundleUtil.deepCopy(mBundle);
         return new GenericDocument.Builder<>(clonedBundle);
@@ -999,7 +1002,6 @@
                 builder.append("\n");
                 builder.decreaseIndentLevel();
             }
-            builder.append("]");
         } else {
             int propertyArrLength = Array.getLength(property);
             for (int i = 0; i < propertyArrLength; i++) {
@@ -1013,11 +1015,10 @@
                 }
                 if (i != propertyArrLength - 1) {
                     builder.append(", ");
-                } else {
-                    builder.append("]");
                 }
             }
         }
+        builder.append("]");
     }
 
     /**
@@ -1082,6 +1083,18 @@
         }
 
         /**
+         * Creates a new {@link GenericDocument.Builder} from the given GenericDocument.
+         *
+         * <p>The GenericDocument is deep copied, i.e. changes to the new GenericDocument returned
+         * by this function will NOT affect the original GenericDocument.
+         *
+         * @hide
+         */
+        public Builder(@NonNull GenericDocument document) {
+            this(BundleUtil.deepCopy(document.getBundle()));
+        }
+
+        /**
          * Sets the app-defined namespace this document resides in, changing the value provided in
          * the constructor. No special values are reserved or understood by the infrastructure.
          *
diff --git a/framework/java/external/android/app/appsearch/JoinSpec.java b/framework/java/external/android/app/appsearch/JoinSpec.java
index f4e62fc..7b487b7 100644
--- a/framework/java/external/android/app/appsearch/JoinSpec.java
+++ b/framework/java/external/android/app/appsearch/JoinSpec.java
@@ -277,9 +277,9 @@
          * Sets the query and the SearchSpec for the documents being joined. This will score and
          * rank the joined documents as well as filter the joined documents.
          *
-         * <p>If {@link SearchSpec.RankingStrategy#RANKING_STRATEGY_JOIN_AGGREGATE_SCORE} is set in
-         * the outer {@link SearchSpec}, the resulting signals will be used to rank the parent
-         * documents. Note that the aggregation strategy also needs to be set with {@link
+         * <p>If {@link SearchSpec#RANKING_STRATEGY_JOIN_AGGREGATE_SCORE} is set in the outer {@link
+         * SearchSpec}, the resulting signals will be used to rank the parent documents. Note that
+         * the aggregation strategy also needs to be set with {@link
          * JoinSpec.Builder#setAggregationScoringStrategy}, otherwise the default will be {@link
          * JoinSpec#AGGREGATION_SCORING_OUTER_RESULT_RANKING_SIGNAL}, which will just use the parent
          * documents ranking signal.
diff --git a/framework/java/external/android/app/appsearch/SearchSpec.java b/framework/java/external/android/app/appsearch/SearchSpec.java
index 68d0946..bab25ad 100644
--- a/framework/java/external/android/app/appsearch/SearchSpec.java
+++ b/framework/java/external/android/app/appsearch/SearchSpec.java
@@ -487,17 +487,17 @@
         return mBundle.getString(ADVANCED_RANKING_EXPRESSION, "");
     }
 
-    /** Returns whether the {@link Features#NUMERIC_SEARCH} feature is enabled. */
+    /** Returns whether the NUMERIC_SEARCH feature is enabled. */
     public boolean isNumericSearchEnabled() {
         return getEnabledFeatures().contains(FeatureConstants.NUMERIC_SEARCH);
     }
 
-    /** Returns whether the {@link Features#VERBATIM_SEARCH} feature is enabled. */
+    /** Returns whether the VERBATIM_SEARCH feature is enabled. */
     public boolean isVerbatimSearchEnabled() {
         return getEnabledFeatures().contains(FeatureConstants.VERBATIM_SEARCH);
     }
 
-    /** Returns whether the {@link Features#LIST_FILTER_QUERY_LANGUAGE} feature is enabled. */
+    /** Returns whether the LIST_FILTER_QUERY_LANGUAGE feature is enabled. */
     public boolean isListFilterQueryLanguageEnabled() {
         return getEnabledFeatures().contains(FeatureConstants.LIST_FILTER_QUERY_LANGUAGE);
     }
@@ -1199,8 +1199,7 @@
         }
 
         /**
-         * Sets the {@link Features#NUMERIC_SEARCH} feature as enabled/disabled according to the
-         * enabled parameter.
+         * Sets the NUMERIC_SEARCH feature as enabled/disabled according to the enabled parameter.
          *
          * @param enabled Enables the feature if true, otherwise disables it.
          *     <p>If disabled, disallows use of {@link
@@ -1214,8 +1213,7 @@
         }
 
         /**
-         * Sets the {@link Features#VERBATIM_SEARCH} feature as enabled/disabled according to the
-         * enabled parameter.
+         * Sets the VERBATIM_SEARCH feature as enabled/disabled according to the enabled parameter.
          *
          * @param enabled Enables the feature if true, otherwise disables it
          *     <p>If disabled, disallows use of {@link
@@ -1232,8 +1230,8 @@
         }
 
         /**
-         * Sets the {@link Features#LIST_FILTER_QUERY_LANGUAGE} feature as enabled/disabled
-         * according to the enabled parameter.
+         * Sets the LIST_FILTER_QUERY_LANGUAGE feature as enabled/disabled according to the enabled
+         * parameter.
          *
          * @param enabled Enables the feature if true, otherwise disables it.
          *     <p>This feature covers the expansion of the query language to conform to the
diff --git a/framework/java/external/android/app/appsearch/stats/SchemaMigrationStats.java b/framework/java/external/android/app/appsearch/stats/SchemaMigrationStats.java
index b2b9ff6..9efef2b 100644
--- a/framework/java/external/android/app/appsearch/stats/SchemaMigrationStats.java
+++ b/framework/java/external/android/app/appsearch/stats/SchemaMigrationStats.java
@@ -39,7 +39,7 @@
     @NonNull
     public static final SchemaMigrationStatsCreator CREATOR = new SchemaMigrationStatsCreator();
 
-    // Indicate how a SetSchema call relative to SchemaMigration case.
+    // Indicate the how a SetSchema call relative to SchemaMigration case.
     @IntDef(
             value = {
                 NO_MIGRATION,
diff --git a/service/java/com/android/server/appsearch/FrameworkAppSearchConfigImpl.java b/service/java/com/android/server/appsearch/FrameworkAppSearchConfigImpl.java
index 4333384..440a3c3 100644
--- a/service/java/com/android/server/appsearch/FrameworkAppSearchConfigImpl.java
+++ b/service/java/com/android/server/appsearch/FrameworkAppSearchConfigImpl.java
@@ -104,6 +104,10 @@
         "icing_lite_index_sort_size";
     public static final String KEY_SHOULD_RETRIEVE_PARENT_INFO =
         "should_retrieve_parent_info";
+    public static final String KEY_USE_NEW_QUALIFIED_ID_JOIN_INDEX =
+        "use_new_qualified_id_join_index";
+    public static final String KEY_BUILD_PROPERTY_EXISTENCE_METADATA_HITS =
+        "build_property_existence_metadata_hits";
 
     /**
      * This config does not need to be cached in FrameworkAppSearchConfigImpl as it is only accessed
@@ -147,6 +151,8 @@
             KEY_ICING_LITE_INDEX_SORT_AT_INDEXING,
             KEY_ICING_LITE_INDEX_SORT_SIZE,
             KEY_SHOULD_RETRIEVE_PARENT_INFO,
+            KEY_USE_NEW_QUALIFIED_ID_JOIN_INDEX,
+            KEY_BUILD_PROPERTY_EXISTENCE_METADATA_HITS,
     };
 
     // Lock needed for all the operations in this class.
@@ -549,6 +555,22 @@
     }
 
     @Override
+    public boolean getUseNewQualifiedIdJoinIndex() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getBoolean(
+                KEY_USE_NEW_QUALIFIED_ID_JOIN_INDEX,
+                DEFAULT_USE_NEW_QUALIFIED_ID_JOIN_INDEX);
+        }
+    }
+
+    @Override
+    public boolean getBuildPropertyExistenceMetadataHits() {
+        // TODO(b/309826655) Set this value properly in main branch
+        return false; // We never turn this feature on in udc-mainline-prod
+    }
+
+    @Override
     public boolean shouldStoreParentInfoAsSyntheticProperty() {
       // This option is always true in Framework.
       return true;
@@ -773,6 +795,14 @@
                             DEFAULT_SHOULD_RETRIEVE_PARENT_INFO));
                 }
                 break;
+            case KEY_USE_NEW_QUALIFIED_ID_JOIN_INDEX:
+                synchronized (mLock) {
+                    mBundleLocked.putBoolean(key, properties.getBoolean(key, true));
+                }
+                break;
+            case KEY_BUILD_PROPERTY_EXISTENCE_METADATA_HITS:
+                // TODO(b/309826655) Set this value properly in main branch
+                // fall throw to default since we never turn this feature on in udc-mainline-prod
             default:
                 break;
         }
diff --git a/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 71a4f11..1e70a8a 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -315,6 +315,9 @@
                                     mConfig.getIntegerIndexBucketSplitThreshold())
                             .setLiteIndexSortAtIndexing(mConfig.getLiteIndexSortAtIndexing())
                             .setLiteIndexSortSize(mConfig.getLiteIndexSortSize())
+                            .setUseNewQualifiedIdJoinIndex(mConfig.getUseNewQualifiedIdJoinIndex())
+                            .setBuildPropertyExistenceMetadataHits(
+                                    mConfig.getBuildPropertyExistenceMetadataHits())
                             .build();
             LogUtil.piiTrace(TAG, "Constructing IcingSearchEngine, request", options);
             mIcingSearchEngineLocked = new IcingSearchEngine(options);
@@ -566,8 +569,7 @@
                             (int) (getOldSchemaEndTimeMillis - getOldSchemaStartTimeMillis));
         }
 
-        int getOldSchemaObserverStartTimeMillis =
-                (int) (SystemClock.elapsedRealtime() - getOldSchemaEndTimeMillis);
+        long getOldSchemaObserverStartTimeMillis = SystemClock.elapsedRealtime();
         // Cache some lookup tables to help us work with the old schema
         Set<AppSearchSchema> oldSchemaTypes = oldSchema.getSchemas();
         Map<String, AppSearchSchema> oldSchemaNameToType = new ArrayMap<>(oldSchemaTypes.size());
@@ -732,7 +734,7 @@
             int version,
             @Nullable SetSchemaStats.Builder setSchemaStatsBuilder)
             throws AppSearchException {
-        long setRewriteSchemaLatencyMillis = SystemClock.elapsedRealtime();
+        long setRewriteSchemaLatencyStartTimeMillis = SystemClock.elapsedRealtime();
         SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder();
 
         SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
@@ -752,7 +754,7 @@
         long rewriteSchemaEndTimeMillis = SystemClock.elapsedRealtime();
         if (setSchemaStatsBuilder != null) {
             setSchemaStatsBuilder.setRewriteSchemaLatencyMillis(
-                    (int) (rewriteSchemaEndTimeMillis - setRewriteSchemaLatencyMillis));
+                    (int) (rewriteSchemaEndTimeMillis - setRewriteSchemaLatencyStartTimeMillis));
         }
 
         // Apply schema
diff --git a/service/java/com/android/server/appsearch/external/localstorage/IcingOptionsConfig.java b/service/java/com/android/server/appsearch/external/localstorage/IcingOptionsConfig.java
index c3c59a4..e2b569b 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/IcingOptionsConfig.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/IcingOptionsConfig.java
@@ -68,6 +68,10 @@
      */
     int DEFAULT_LITE_INDEX_SORT_SIZE = 8192; // 8Kib
 
+    boolean DEFAULT_USE_NEW_QUALIFIED_ID_JOIN_INDEX = false;
+
+    boolean DEFAULT_BUILD_PROPERTY_EXISTENCE_METADATA_HITS = false;
+
     /**
      * The maximum allowable token length. All tokens in excess of this size will be truncated to
      * max_token_length before being indexed.
@@ -201,4 +205,19 @@
      * <p>Setting a lower sort size reduces querying latency at the expense of indexing latency.
      */
     int getLiteIndexSortSize();
+
+    /**
+     * Flag for {@link com.google.android.icing.proto.IcingSearchEngineOptions}.
+     *
+     * <p>Whether to use the new qualified Id join index.
+     */
+    boolean getUseNewQualifiedIdJoinIndex();
+
+    /**
+     * Flag for {@link com.google.android.icing.proto.IcingSearchEngineOptions}.
+     *
+     * <p>Whether to build the metadata hits used for property existence check, which is required to
+     * support the hasProperty function in advanced query.
+     */
+    boolean getBuildPropertyExistenceMetadataHits();
 }
diff --git a/service/java/com/android/server/appsearch/external/localstorage/ObserverManager.java b/service/java/com/android/server/appsearch/external/localstorage/ObserverManager.java
index b34fac3..4b711ae 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/ObserverManager.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/ObserverManager.java
@@ -387,14 +387,13 @@
         Map<String, Set<String>> schemaChanges = observerInfo.mSchemaChanges;
         Map<DocumentChangeGroupKey, Set<String>> documentChanges = observerInfo.mDocumentChanges;
         if (schemaChanges.isEmpty() && documentChanges.isEmpty()) {
+            // There is nothing to send, return early.
             return;
         }
-        if (!schemaChanges.isEmpty()) {
-            observerInfo.mSchemaChanges = new ArrayMap<>();
-        }
-        if (!documentChanges.isEmpty()) {
-            observerInfo.mDocumentChanges = new ArrayMap<>();
-        }
+        // Clean the pending changes in the observer. We already copy pending changes to local
+        // variables.
+        observerInfo.mSchemaChanges = new ArrayMap<>();
+        observerInfo.mDocumentChanges = new ArrayMap<>();
 
         // Dispatch the pending changes
         observerInfo.mExecutor.execute(
diff --git a/synced_jetpack_sha.txt b/synced_jetpack_sha.txt
index 9f457ba..26e7658 100644
--- a/synced_jetpack_sha.txt
+++ b/synced_jetpack_sha.txt
@@ -1 +1 @@
-ca831ece697f8b14a0861fd2f19f3654d79097fa
+0c8ad0cdfa25a4d2f80554d0e8d45fe34ce16aa6
diff --git a/testing/coretests/src/android/app/appsearch/external/app/AppSearchSessionInternalTestBase.java b/testing/coretests/src/android/app/appsearch/external/app/AppSearchSessionInternalTestBase.java
index 43475bd..d2e52af 100644
--- a/testing/coretests/src/android/app/appsearch/external/app/AppSearchSessionInternalTestBase.java
+++ b/testing/coretests/src/android/app/appsearch/external/app/AppSearchSessionInternalTestBase.java
@@ -1504,7 +1504,9 @@
                                 .addGenericDocuments(personDoc, artistDoc, emailDoc)
                                 .build()));
         GenericDocument artistDocWithParent =
-                artistDoc.toBuilder().setParentTypes(Collections.singletonList("Person")).build();
+                new GenericDocument.Builder<>(artistDoc)
+                        .setParentTypes(Collections.singletonList("Person"))
+                        .build();
 
         // Query for the documents
         SearchResultsShim searchResults =
@@ -1717,9 +1719,13 @@
                                 .addGenericDocuments(artistDoc, messageDoc)
                                 .build()));
         GenericDocument expectedArtistDoc =
-                artistDoc.toBuilder().setParentTypes(Collections.singletonList("Person")).build();
+                new GenericDocument.Builder<>(artistDoc)
+                        .setParentTypes(Collections.singletonList("Person"))
+                        .build();
         GenericDocument expectedMessageDoc =
-                messageDoc.toBuilder().setPropertyDocument("sender", expectedArtistDoc).build();
+                new GenericDocument.Builder<>(messageDoc)
+                        .setPropertyDocument("sender", expectedArtistDoc)
+                        .build();
 
         // Query for the documents
         SearchResultsShim searchResults =
@@ -1781,13 +1787,17 @@
                                 .build()));
 
         GenericDocument expectedDocA =
-                docA.toBuilder()
+                new GenericDocument.Builder<>(docA)
                         .setParentTypes(new ArrayList<>(Arrays.asList("C", "B", "D")))
                         .build();
         GenericDocument expectedDocB =
-                docB.toBuilder().setParentTypes(Collections.singletonList("D")).build();
+                new GenericDocument.Builder<>(docB)
+                        .setParentTypes(Collections.singletonList("D"))
+                        .build();
         GenericDocument expectedDocC =
-                docC.toBuilder().setParentTypes(new ArrayList<>(Arrays.asList("B", "D"))).build();
+                new GenericDocument.Builder<>(docC)
+                        .setParentTypes(new ArrayList<>(Arrays.asList("B", "D")))
+                        .build();
         // Query for the documents
         SearchResultsShim searchResults = mDb1.search("", new SearchSpec.Builder().build());
         List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
diff --git a/testing/coretests/src/android/app/appsearch/external/app/GenericDocumentInternalTest.java b/testing/coretests/src/android/app/appsearch/external/app/GenericDocumentInternalTest.java
index ab8360c..08cb351 100644
--- a/testing/coretests/src/android/app/appsearch/external/app/GenericDocumentInternalTest.java
+++ b/testing/coretests/src/android/app/appsearch/external/app/GenericDocumentInternalTest.java
@@ -107,4 +107,56 @@
         assertThat(outDoc.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
                 .isEqualTo(new byte[][] {{3, 4}});
     }
+
+    @Test
+    public void testGenericDocumentBuilderDoesNotMutateOriginal() {
+        GenericDocument oldDoc =
+                new GenericDocument.Builder<>("namespace", "id1", "schema1")
+                        .setParentTypes(new ArrayList<>(Arrays.asList("Class1", "Class2")))
+                        .setScore(42)
+                        .setPropertyString("propString", "Hello")
+                        .setPropertyBytes("propBytes", new byte[][] {{1, 2}})
+                        .setPropertyDocument(
+                                "propDocument",
+                                new GenericDocument.Builder<>("namespace", "id2", "schema2")
+                                        .setPropertyString("propString", "Goodbye")
+                                        .setPropertyBytes("propBytes", new byte[][] {{3, 4}})
+                                        .build())
+                        .build();
+
+        GenericDocument newDoc =
+                new GenericDocument.Builder<>(oldDoc)
+                        .setParentTypes(new ArrayList<>(Arrays.asList("Class3", "Class4")))
+                        .setPropertyBytes("propBytes", new byte[][] {{1, 2}})
+                        .setPropertyDocument(
+                                "propDocument",
+                                new GenericDocument.Builder<>("namespace", "id3", "schema3")
+                                        .setPropertyString("propString", "Bye")
+                                        .setPropertyBytes("propBytes", new byte[][] {{5, 6}})
+                                        .build())
+                        .build();
+
+        // Check that the original GenericDocument is unmodified.
+        assertThat(oldDoc.getParentTypes()).isEqualTo(Arrays.asList("Class1", "Class2"));
+        assertThat(oldDoc.getScore()).isEqualTo(42);
+        assertThat(oldDoc.getPropertyString("propString")).isEqualTo("Hello");
+        assertThat(oldDoc.getPropertyBytesArray("propBytes")).isEqualTo(new byte[][] {{1, 2}});
+        assertThat(oldDoc.getPropertyDocument("propDocument").getPropertyString("propString"))
+                .isEqualTo("Goodbye");
+        assertThat(oldDoc.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
+                .isEqualTo(new byte[][] {{3, 4}});
+
+        // Check that the new GenericDocument has modified the original fields correctly.
+        assertThat(newDoc.getParentTypes()).isEqualTo(Arrays.asList("Class3", "Class4"));
+        assertThat(newDoc.getPropertyBytesArray("propBytes")).isEqualTo(new byte[][] {{1, 2}});
+        assertThat(newDoc.getPropertyDocument("propDocument").getPropertyString("propString"))
+                .isEqualTo("Bye");
+        assertThat(newDoc.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
+                .isEqualTo(new byte[][] {{5, 6}});
+
+        // Check that the new GenericDocument copies fields that aren't set.
+        assertThat(oldDoc.getScore()).isEqualTo(newDoc.getScore());
+        assertThat(oldDoc.getPropertyString("propString"))
+                .isEqualTo(newDoc.getPropertyString("propString"));
+    }
 }
diff --git a/testing/mockingservicestests/src/com/android/server/appsearch/FrameworkAppSearchConfigTest.java b/testing/mockingservicestests/src/com/android/server/appsearch/FrameworkAppSearchConfigTest.java
index 0faa310..908f188 100644
--- a/testing/mockingservicestests/src/com/android/server/appsearch/FrameworkAppSearchConfigTest.java
+++ b/testing/mockingservicestests/src/com/android/server/appsearch/FrameworkAppSearchConfigTest.java
@@ -33,6 +33,7 @@
 import static com.android.server.appsearch.FrameworkAppSearchConfig.DEFAULT_INTEGER_INDEX_BUCKET_SPLIT_THRESHOLD;
 import static com.android.server.appsearch.FrameworkAppSearchConfig.DEFAULT_LITE_INDEX_SORT_AT_INDEXING;
 import static com.android.server.appsearch.FrameworkAppSearchConfig.DEFAULT_LITE_INDEX_SORT_SIZE;
+import static com.android.server.appsearch.FrameworkAppSearchConfigImpl.KEY_BUILD_PROPERTY_EXISTENCE_METADATA_HITS;
 import static com.android.server.appsearch.FrameworkAppSearchConfigImpl.KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS;
 import static com.android.server.appsearch.FrameworkAppSearchConfigImpl.KEY_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS;
 import static com.android.server.appsearch.FrameworkAppSearchConfigImpl.KEY_SAMPLING_INTERVAL_DEFAULT;
@@ -66,7 +67,10 @@
 import static com.android.server.appsearch.FrameworkAppSearchConfigImpl.KEY_RATE_LIMIT_TASK_QUEUE_TOTAL_CAPACITY;
 import static com.android.server.appsearch.FrameworkAppSearchConfigImpl.KEY_RATE_LIMIT_TASK_QUEUE_PER_PACKAGE_CAPACITY_PERCENTAGE;
 import static com.android.server.appsearch.FrameworkAppSearchConfigImpl.KEY_RATE_LIMIT_API_COSTS;
+import static com.android.server.appsearch.FrameworkAppSearchConfigImpl.KEY_USE_NEW_QUALIFIED_ID_JOIN_INDEX;
 
+import static com.android.server.appsearch.external.localstorage.IcingOptionsConfig.DEFAULT_BUILD_PROPERTY_EXISTENCE_METADATA_HITS;
+import static com.android.server.appsearch.external.localstorage.IcingOptionsConfig.DEFAULT_USE_NEW_QUALIFIED_ID_JOIN_INDEX;
 import static com.google.common.truth.Truth.assertThat;
 
 import android.provider.DeviceConfig;
@@ -161,6 +165,10 @@
         assertThat(appSearchConfig.getLiteIndexSortAtIndexing()).isEqualTo(
                 DEFAULT_LITE_INDEX_SORT_AT_INDEXING);
         assertThat(appSearchConfig.getLiteIndexSortSize()).isEqualTo(DEFAULT_LITE_INDEX_SORT_SIZE);
+        assertThat(appSearchConfig.getUseNewQualifiedIdJoinIndex())
+            .isEqualTo(DEFAULT_USE_NEW_QUALIFIED_ID_JOIN_INDEX);
+        assertThat(appSearchConfig.getBuildPropertyExistenceMetadataHits())
+            .isEqualTo(DEFAULT_BUILD_PROPERTY_EXISTENCE_METADATA_HITS);
     }
 
     @Test
@@ -824,6 +832,44 @@
     }
 
     @Test
+    public void testCustomizedValue_joins() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+            KEY_USE_NEW_QUALIFIED_ID_JOIN_INDEX, Boolean.toString(true), false);
+
+        FrameworkAppSearchConfig appSearchConfig =
+            FrameworkAppSearchConfigImpl.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getUseNewQualifiedIdJoinIndex()).isEqualTo(true);
+    }
+
+    @Test
+    public void testCustomizedValueOverride_joins() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+            KEY_USE_NEW_QUALIFIED_ID_JOIN_INDEX, Boolean.toString(true), false);
+
+        FrameworkAppSearchConfig appSearchConfig =
+            FrameworkAppSearchConfigImpl.create(DIRECT_EXECUTOR);
+
+        // Override
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                KEY_USE_NEW_QUALIFIED_ID_JOIN_INDEX, Boolean.toString(false), false);
+
+        assertThat(appSearchConfig.getUseNewQualifiedIdJoinIndex()).isEqualTo(false);
+    }
+
+    @Test
+    public void testCustomizedValue_hasProperty_alwaysFalse() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+            KEY_BUILD_PROPERTY_EXISTENCE_METADATA_HITS, Boolean.toString(true), false);
+
+        FrameworkAppSearchConfig appSearchConfig =
+            FrameworkAppSearchConfigImpl.create(DIRECT_EXECUTOR);
+
+        // We never turn on this flag in udc-mainline-prod.
+        assertThat(appSearchConfig.getBuildPropertyExistenceMetadataHits()).isEqualTo(false);
+    }
+
+    @Test
     public void testNotUsable_afterClose() {
         FrameworkAppSearchConfig appSearchConfig =
             FrameworkAppSearchConfigImpl.create(DIRECT_EXECUTOR);
@@ -920,5 +966,8 @@
         Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
                 IllegalStateException.class,
                 () -> appSearchConfig.getLiteIndexSortSize());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getUseNewQualifiedIdJoinIndex());
     }
 }
diff --git a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
index 9d01cf0..788bf98 100644
--- a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
+++ b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java
@@ -217,19 +217,10 @@
                         .addParentTypes("Email")
                         .addParentTypes("Message")
                         .build();
-        SchemaTypeConfigProto alternativeExpectedSchemaProto =
-                SchemaTypeConfigProto.newBuilder()
-                        .setSchemaType("EmailMessage")
-                        .setVersion(12345)
-                        .addParentTypes("Message")
-                        .addParentTypes("Email")
-                        .build();
 
         assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(schema, /*version=*/ 12345))
-                .isAnyOf(expectedSchemaProto, alternativeExpectedSchemaProto);
+                .isEqualTo(expectedSchemaProto);
         assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedSchemaProto)).isEqualTo(schema);
-        assertThat(SchemaToProtoConverter.toAppSearchSchema(alternativeExpectedSchemaProto))
-                .isEqualTo(schema);
     }
 
     @Test