1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.appsearch.app;
18 
19 import android.annotation.SuppressLint;
20 import android.os.Bundle;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 
24 import androidx.annotation.IntDef;
25 import androidx.annotation.IntRange;
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 import androidx.annotation.OptIn;
29 import androidx.annotation.RequiresFeature;
30 import androidx.annotation.RestrictTo;
31 import androidx.appsearch.annotation.CanIgnoreReturnValue;
32 import androidx.appsearch.annotation.Document;
33 import androidx.appsearch.exceptions.AppSearchException;
34 import androidx.appsearch.flags.FlaggedApi;
35 import androidx.appsearch.flags.Flags;
36 import androidx.appsearch.safeparcel.AbstractSafeParcelable;
37 import androidx.appsearch.safeparcel.SafeParcelable;
38 import androidx.appsearch.safeparcel.stub.StubCreators.SearchSpecCreator;
39 import androidx.appsearch.util.BundleUtil;
40 import androidx.collection.ArrayMap;
41 import androidx.collection.ArraySet;
42 import androidx.core.util.Preconditions;
43 
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.RetentionPolicy;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.Collection;
49 import java.util.Collections;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Objects;
53 import java.util.Set;
54 
55 /**
56  * This class represents the specification logic for AppSearch. It can be used to set the type of
57  * search, like prefix or exact only or apply filters to search for a specific schema type only etc.
58  */
59 @SafeParcelable.Class(creator = "SearchSpecCreator")
60 // TODO(b/384721898): Switch to JSpecify annotations
61 @SuppressWarnings({"HiddenSuperclass", "JSpecifyNullness"})
62 public final class SearchSpec extends AbstractSafeParcelable {
63 
64     /**  Creator class for {@link SearchSpec}. */
65     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
66     @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2)
67     public static final @NonNull Parcelable.Creator<SearchSpec> CREATOR =
68             new SearchSpecCreator();
69 
70     /**
71      * Schema type to be used in {@link SearchSpec.Builder#addProjection} to apply
72      * property paths to all results, excepting any types that have had their own, specific
73      * property paths set.
74      *
75      * @deprecated use {@link #SCHEMA_TYPE_WILDCARD} instead.
76      */
77     @Deprecated
78     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
79     public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
80 
81     /**
82      * Schema type to be used in {@link SearchSpec.Builder#addFilterProperties(String, Collection)}
83      * and {@link SearchSpec.Builder#addProjection} to apply property paths to all results,
84      * excepting any types that have had their own, specific property paths set.
85      */
86     @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_PROPERTIES)
87     public static final String SCHEMA_TYPE_WILDCARD = "*";
88 
89     @Field(id = 1, getter = "getTermMatch")
90     private final int mTermMatchType;
91 
92     @Field(id = 2, getter = "getFilterSchemas")
93     private final List<String> mSchemas;
94 
95     @Field(id = 3, getter = "getFilterNamespaces")
96     private final List<String> mNamespaces;
97 
98     @Field(id = 4)
99     final Bundle mTypePropertyFilters;
100 
101     @Field(id = 5, getter = "getFilterPackageNames")
102     private final List<String> mPackageNames;
103 
104     @Field(id = 6, getter = "getResultCountPerPage")
105     private final int mResultCountPerPage;
106 
107     @Field(id = 7, getter = "getRankingStrategy")
108     @RankingStrategy
109     private final int mRankingStrategy;
110 
111     @Field(id = 8, getter = "getOrder")
112     @Order
113     private final int mOrder;
114 
115     @Field(id = 9, getter = "getSnippetCount")
116     private final int mSnippetCount;
117 
118     @Field(id = 10, getter = "getSnippetCountPerProperty")
119     private final int mSnippetCountPerProperty;
120 
121     @Field(id = 11, getter = "getMaxSnippetSize")
122     private final int mMaxSnippetSize;
123 
124     @Field(id = 12)
125     final Bundle mProjectionTypePropertyMasks;
126 
127     @Field(id = 13, getter = "getResultGroupingTypeFlags")
128     @GroupingType
129     private final int mResultGroupingTypeFlags;
130 
131     @Field(id = 14, getter = "getResultGroupingLimit")
132     private final int mGroupingLimit;
133 
134     @Field(id = 15)
135     final Bundle mTypePropertyWeightsField;
136 
137     @Field(id = 16, getter = "getJoinSpec")
138     private final @Nullable JoinSpec mJoinSpec;
139 
140     @Field(id = 17, getter = "getAdvancedRankingExpression")
141     private final String mAdvancedRankingExpression;
142 
143     @Field(id = 18, getter = "getEnabledFeatures")
144     private final List<String> mEnabledFeatures;
145 
146     @Field(id = 19, getter = "getSearchSourceLogTag")
147     private final @Nullable String mSearchSourceLogTag;
148 
149     @Field(id = 20, getter = "getEmbeddingParameters")
150     private final @NonNull List<EmbeddingVector> mEmbeddingParameters;
151 
152     @Field(id = 21, getter = "getDefaultEmbeddingSearchMetricType")
153     private final int mDefaultEmbeddingSearchMetricType;
154 
155     @Field(id = 22, getter = "getInformationalRankingExpressions")
156     private final @NonNull List<String> mInformationalRankingExpressions;
157 
158     @Field(id = 23, getter = "getSearchStringParameters")
159     private final @NonNull List<String> mSearchStringParameters;
160 
161     /**
162      * Holds the list of document ids to search over.
163      *
164      * <p>If empty, the query will search over all documents.
165      */
166     @Field(id = 24, getter = "getFilterDocumentIds")
167     private final @NonNull List<String> mFilterDocumentIds;
168 
169     /**
170      * Whether to retrieve embedding match info for the query.
171      */
172     @Field(id = 25, getter = "shouldRetrieveEmbeddingMatchInfos")
173     private final boolean mRetrieveEmbeddingMatchInfos;
174 
175     /**
176      * Default number of documents per page.
177      *
178      * @exportToFramework:hide
179      */
180     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
181     public static final int DEFAULT_NUM_PER_PAGE = 10;
182 
183     // TODO(b/170371356): In framework, we may want these limits to be flag controlled.
184     //  If that happens, the @IntRange() directives in this class may have to change.
185     private static final int MAX_NUM_PER_PAGE = 10_000;
186     private static final int MAX_SNIPPET_COUNT = 10_000;
187     private static final int MAX_SNIPPET_PER_PROPERTY_COUNT = 10_000;
188     private static final int MAX_SNIPPET_SIZE_LIMIT = 10_000;
189 
190     /**
191      * Term Match Type for the query.
192      *
193      * @exportToFramework:hide
194      */
195     // NOTE: The integer values of these constants must match the proto enum constants in
196     // {@link com.google.android.icing.proto.SearchSpecProto.termMatchType}
197     @RestrictTo(RestrictTo.Scope.LIBRARY)
198     @IntDef(value = {
199             TERM_MATCH_EXACT_ONLY,
200             TERM_MATCH_PREFIX
201     })
202     @Retention(RetentionPolicy.SOURCE)
203     public @interface TermMatch {
204     }
205 
206     /**
207      * Query terms will only match exact tokens in the index.
208      * <p>For example, a query term "foo" will only match indexed token "foo", and not "foot" or
209      * "football".
210      */
211     public static final int TERM_MATCH_EXACT_ONLY = 1;
212     /**
213      * Query terms will match indexed tokens when the query term is a prefix of the token.
214      * <p>For example, a query term "foo" will match indexed tokens like "foo", "foot", and
215      * "football".
216      */
217     public static final int TERM_MATCH_PREFIX = 2;
218 
219     /**
220      * Ranking Strategy for query result.
221      *
222      * @exportToFramework:hide
223      */
224     // NOTE: The integer values of these constants must match the proto enum constants in
225     // {@link ScoringSpecProto.RankingStrategy.Code}
226     @RestrictTo(RestrictTo.Scope.LIBRARY)
227     @IntDef(value = {
228             RANKING_STRATEGY_NONE,
229             RANKING_STRATEGY_DOCUMENT_SCORE,
230             RANKING_STRATEGY_CREATION_TIMESTAMP,
231             RANKING_STRATEGY_RELEVANCE_SCORE,
232             RANKING_STRATEGY_USAGE_COUNT,
233             RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP,
234             RANKING_STRATEGY_SYSTEM_USAGE_COUNT,
235             RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP,
236             RANKING_STRATEGY_JOIN_AGGREGATE_SCORE,
237             RANKING_STRATEGY_ADVANCED_RANKING_EXPRESSION,
238     })
239     @Retention(RetentionPolicy.SOURCE)
240     public @interface RankingStrategy {
241     }
242 
243     /** No Ranking, results are returned in arbitrary order. */
244     public static final int RANKING_STRATEGY_NONE = 0;
245     /** Ranked by app-provided document scores. */
246     public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1;
247     /** Ranked by document creation timestamps. */
248     public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2;
249     /** Ranked by document relevance score. */
250     public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3;
251     /** Ranked by number of usages, as reported by the app. */
252     public static final int RANKING_STRATEGY_USAGE_COUNT = 4;
253     /** Ranked by timestamp of last usage, as reported by the app. */
254     public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5;
255     /** Ranked by number of usages from a system UI surface. */
256     public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6;
257     /** Ranked by timestamp of last usage from a system UI surface. */
258     public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7;
259     /**
260      * Ranked by the aggregated ranking signal of the joined documents.
261      *
262      * <p> Which aggregation strategy is used to determine a ranking signal is specified in the
263      * {@link JoinSpec} set by {@link Builder#setJoinSpec}. This ranking strategy may not be used
264      * if no {@link JoinSpec} is provided.
265      *
266      * @see Builder#build
267      */
268     public static final int RANKING_STRATEGY_JOIN_AGGREGATE_SCORE = 8;
269     /** Ranked by the advanced ranking expression provided. */
270     public static final int RANKING_STRATEGY_ADVANCED_RANKING_EXPRESSION = 9;
271 
272     /**
273      * Order for query result.
274      *
275      * @exportToFramework:hide
276      */
277     // NOTE: The integer values of these constants must match the proto enum constants in
278     // {@link ScoringSpecProto.Order.Code}
279     @RestrictTo(RestrictTo.Scope.LIBRARY)
280     @IntDef(value = {
281             ORDER_DESCENDING,
282             ORDER_ASCENDING
283     })
284     @Retention(RetentionPolicy.SOURCE)
285     public @interface Order {
286     }
287 
288     /** Search results will be returned in a descending order. */
289     public static final int ORDER_DESCENDING = 0;
290     /** Search results will be returned in an ascending order. */
291     public static final int ORDER_ASCENDING = 1;
292 
293     /**
294      * Grouping type for result limits.
295      *
296      * @exportToFramework:hide
297      */
298     @IntDef(flag = true, value = {
299             GROUPING_TYPE_PER_PACKAGE,
300             GROUPING_TYPE_PER_NAMESPACE,
301             GROUPING_TYPE_PER_SCHEMA
302     })
303     @RestrictTo(RestrictTo.Scope.LIBRARY)
304     @Retention(RetentionPolicy.SOURCE)
305     public @interface GroupingType {
306     }
307     /**
308      * Results should be grouped together by package for the purpose of enforcing a limit on the
309      * number of results returned per package.
310      */
311     public static final int GROUPING_TYPE_PER_PACKAGE = 1 << 0;
312     /**
313      * Results should be grouped together by namespace for the purpose of enforcing a limit on the
314      * number of results returned per namespace.
315      */
316     public static final int GROUPING_TYPE_PER_NAMESPACE = 1 << 1;
317     /**
318      * Results should be grouped together by schema type for the purpose of enforcing a limit on the
319      * number of results returned per schema type.
320      */
321     @RequiresFeature(
322             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
323             name = Features.SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA)
324     @FlaggedApi(Flags.FLAG_ENABLE_GROUPING_TYPE_PER_SCHEMA)
325     public static final int GROUPING_TYPE_PER_SCHEMA = 1 << 2;
326 
327     /**
328      * Type of scoring used to calculate similarity for embedding vectors. For details of each, see
329      * comments above each value.
330      *
331      * @exportToFramework:hide
332      */
333     // NOTE: The integer values of these constants must match the proto enum constants in
334     // {@link SearchSpecProto.EmbeddingQueryMetricType.Code}
335     @RestrictTo(RestrictTo.Scope.LIBRARY)
336     @IntDef(value = {
337             EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT,
338             EMBEDDING_SEARCH_METRIC_TYPE_COSINE,
339             EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT,
340             EMBEDDING_SEARCH_METRIC_TYPE_EUCLIDEAN,
341     })
342     @Retention(RetentionPolicy.SOURCE)
343     public @interface EmbeddingSearchMetricType {
344     }
345 
346     /**
347      * Use the default metric set in {@link SearchSpec#getDefaultEmbeddingSearchMetricType()} for
348      * embedding search and ranking.
349      */
350     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
351     public static final int EMBEDDING_SEARCH_METRIC_TYPE_DEFAULT = 0;
352 
353     /**
354      * Cosine similarity as metric for embedding search and ranking.
355      */
356     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
357     public static final int EMBEDDING_SEARCH_METRIC_TYPE_COSINE = 1;
358     /**
359      * Dot product similarity as metric for embedding search and ranking.
360      */
361     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
362     public static final int EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT = 2;
363     /**
364      * Euclidean distance as metric for embedding search and ranking.
365      */
366     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
367     public static final int EMBEDDING_SEARCH_METRIC_TYPE_EUCLIDEAN = 3;
368 
369 
370     @Constructor
SearchSpec( @aramid = 1) int termMatchType, @Param(id = 2) @NonNull List<String> schemas, @Param(id = 3) @NonNull List<String> namespaces, @Param(id = 4) @NonNull Bundle properties, @Param(id = 5) @NonNull List<String> packageNames, @Param(id = 6) int resultCountPerPage, @Param(id = 7) @RankingStrategy int rankingStrategy, @Param(id = 8) @Order int order, @Param(id = 9) int snippetCount, @Param(id = 10) int snippetCountPerProperty, @Param(id = 11) int maxSnippetSize, @Param(id = 12) @NonNull Bundle projectionTypePropertyMasks, @Param(id = 13) int resultGroupingTypeFlags, @Param(id = 14) int groupingLimit, @Param(id = 15) @NonNull Bundle typePropertyWeightsField, @Param(id = 16) @Nullable JoinSpec joinSpec, @Param(id = 17) @NonNull String advancedRankingExpression, @Param(id = 18) @NonNull List<String> enabledFeatures, @Param(id = 19) @Nullable String searchSourceLogTag, @Param(id = 20) @Nullable List<EmbeddingVector> embeddingParameters, @Param(id = 21) int defaultEmbeddingSearchMetricType, @Param(id = 22) @Nullable List<String> informationalRankingExpressions, @Param(id = 23) @Nullable List<String> searchStringParameters, @Param(id = 24) @Nullable List<String> filterDocumentIds, @Param(id = 25) boolean retrieveEmbeddingMatchInfos)371     SearchSpec(
372             @Param(id = 1) int termMatchType,
373             @Param(id = 2) @NonNull List<String> schemas,
374             @Param(id = 3) @NonNull List<String> namespaces,
375             @Param(id = 4) @NonNull Bundle properties,
376             @Param(id = 5) @NonNull List<String> packageNames,
377             @Param(id = 6) int resultCountPerPage,
378             @Param(id = 7) @RankingStrategy int rankingStrategy,
379             @Param(id = 8) @Order int order,
380             @Param(id = 9) int snippetCount,
381             @Param(id = 10) int snippetCountPerProperty,
382             @Param(id = 11) int maxSnippetSize,
383             @Param(id = 12) @NonNull Bundle projectionTypePropertyMasks,
384             @Param(id = 13) int resultGroupingTypeFlags,
385             @Param(id = 14) int groupingLimit,
386             @Param(id = 15) @NonNull Bundle typePropertyWeightsField,
387             @Param(id = 16) @Nullable JoinSpec joinSpec,
388             @Param(id = 17) @NonNull String advancedRankingExpression,
389             @Param(id = 18) @NonNull List<String> enabledFeatures,
390             @Param(id = 19) @Nullable String searchSourceLogTag,
391             @Param(id = 20) @Nullable List<EmbeddingVector> embeddingParameters,
392             @Param(id = 21) int defaultEmbeddingSearchMetricType,
393             @Param(id = 22) @Nullable List<String> informationalRankingExpressions,
394             @Param(id = 23) @Nullable List<String> searchStringParameters,
395             @Param(id = 24) @Nullable List<String> filterDocumentIds,
396             @Param(id = 25) boolean retrieveEmbeddingMatchInfos) {
397         mTermMatchType = termMatchType;
398         mSchemas = Collections.unmodifiableList(Preconditions.checkNotNull(schemas));
399         mNamespaces = Collections.unmodifiableList(Preconditions.checkNotNull(namespaces));
400         mTypePropertyFilters = Preconditions.checkNotNull(properties);
401         mPackageNames = Collections.unmodifiableList(Preconditions.checkNotNull(packageNames));
402         mResultCountPerPage = resultCountPerPage;
403         mRankingStrategy = rankingStrategy;
404         mOrder = order;
405         mSnippetCount = snippetCount;
406         mSnippetCountPerProperty = snippetCountPerProperty;
407         mMaxSnippetSize = maxSnippetSize;
408         mProjectionTypePropertyMasks = Preconditions.checkNotNull(projectionTypePropertyMasks);
409         mResultGroupingTypeFlags = resultGroupingTypeFlags;
410         mGroupingLimit = groupingLimit;
411         mTypePropertyWeightsField = Preconditions.checkNotNull(typePropertyWeightsField);
412         mJoinSpec = joinSpec;
413         mAdvancedRankingExpression = Preconditions.checkNotNull(advancedRankingExpression);
414         mEnabledFeatures = Collections.unmodifiableList(
415                 Preconditions.checkNotNull(enabledFeatures));
416         mSearchSourceLogTag = searchSourceLogTag;
417         if (embeddingParameters != null) {
418             mEmbeddingParameters = Collections.unmodifiableList(embeddingParameters);
419         } else {
420             mEmbeddingParameters = Collections.emptyList();
421         }
422         mDefaultEmbeddingSearchMetricType = defaultEmbeddingSearchMetricType;
423         if (informationalRankingExpressions != null) {
424             mInformationalRankingExpressions = Collections.unmodifiableList(
425                     informationalRankingExpressions);
426         } else {
427             mInformationalRankingExpressions = Collections.emptyList();
428         }
429         mSearchStringParameters =
430                 (searchStringParameters != null)
431                         ? Collections.unmodifiableList(searchStringParameters)
432                         : Collections.emptyList();
433         mFilterDocumentIds =
434                 (filterDocumentIds != null)
435                         ? Collections.unmodifiableList(filterDocumentIds)
436                         : Collections.emptyList();
437         mRetrieveEmbeddingMatchInfos = retrieveEmbeddingMatchInfos;
438     }
439 
440 
441     /** Returns how the query terms should match terms in the index. */
442     @TermMatch
getTermMatch()443     public int getTermMatch() {
444         return mTermMatchType;
445     }
446 
447     /**
448      * Returns the list of schema types to search for.
449      *
450      * <p>If empty, the query will search over all schema types.
451      */
getFilterSchemas()452     public @NonNull List<String> getFilterSchemas() {
453         if (mSchemas == null) {
454             return Collections.emptyList();
455         }
456         return mSchemas;
457     }
458 
459     /**
460      * Returns the map of schema and target properties to search over.
461      *
462      * <p>If empty, will search over all schema and properties.
463      *
464      * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned
465      * by this function, rather than calling it multiple times.
466      */
467     @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_PROPERTIES)
getFilterProperties()468     public @NonNull Map<String, List<String>> getFilterProperties() {
469         Set<String> schemas = mTypePropertyFilters.keySet();
470         Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size());
471         for (String schema : schemas) {
472             typePropertyPathsMap.put(schema, Preconditions.checkNotNull(
473                     mTypePropertyFilters.getStringArrayList(schema)));
474         }
475         return typePropertyPathsMap;
476     }
477 
478     /**
479      * Returns the list of namespaces to search over.
480      *
481      * <p>If empty, the query will search over all namespaces.
482      */
getFilterNamespaces()483     public @NonNull List<String> getFilterNamespaces() {
484         if (mNamespaces == null) {
485             return Collections.emptyList();
486         }
487         return mNamespaces;
488     }
489 
490     /**
491      * Returns the list of package name filters to search over.
492      *
493      * <p>If empty, the query will search over all packages that the caller has access to. If
494      * package names are specified which caller doesn't have access to, then those package names
495      * will be ignored.
496      */
getFilterPackageNames()497     public @NonNull List<String> getFilterPackageNames() {
498         if (mPackageNames == null) {
499             return Collections.emptyList();
500         }
501         return mPackageNames;
502     }
503 
504     /**
505      * Returns the list of document ids to search over.
506      *
507      * <p>If empty, the query will search over all documents.
508      */
509     @ExperimentalAppSearchApi
510     @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_DOCUMENT_IDS)
getFilterDocumentIds()511     public @NonNull List<String> getFilterDocumentIds() {
512         return mFilterDocumentIds;
513     }
514 
515     /** Returns the number of results per page in the result set. */
getResultCountPerPage()516     public int getResultCountPerPage() {
517         return mResultCountPerPage;
518     }
519 
520     /** Returns the ranking strategy. */
521     @RankingStrategy
getRankingStrategy()522     public int getRankingStrategy() {
523         return mRankingStrategy;
524     }
525 
526     /** Returns the order of returned search results (descending or ascending). */
527     @Order
getOrder()528     public int getOrder() {
529         return mOrder;
530     }
531 
532     /** Returns how many documents to generate snippets for. */
getSnippetCount()533     public int getSnippetCount() {
534         return mSnippetCount;
535     }
536 
537     /**
538      * Returns how many matches for each property of a matching document to generate snippets for.
539      */
getSnippetCountPerProperty()540     public int getSnippetCountPerProperty() {
541         return mSnippetCountPerProperty;
542     }
543 
544     /** Returns the maximum size of a snippet in characters. */
getMaxSnippetSize()545     public int getMaxSnippetSize() {
546         return mMaxSnippetSize;
547     }
548 
549     /**
550      * Returns whether to retrieve embedding match infos as a part of
551      * {@link SearchResult#getMatchInfos()}
552      */
553     @FlaggedApi(Flags.FLAG_ENABLE_EMBEDDING_MATCH_INFO)
554     @ExperimentalAppSearchApi
shouldRetrieveEmbeddingMatchInfos()555     public boolean shouldRetrieveEmbeddingMatchInfos() {
556         return mRetrieveEmbeddingMatchInfos;
557     }
558 
559     /**
560      * Returns a map from schema type to property paths to be used for projection.
561      *
562      * <p>If the map is empty, then all properties will be retrieved for all results.
563      *
564      * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned
565      * by this function, rather than calling it multiple times.
566      *
567      * @return A mapping of schema types to lists of projection strings.
568      */
getProjections()569     public @NonNull Map<String, List<String>> getProjections() {
570         Set<String> schemas = mProjectionTypePropertyMasks.keySet();
571         Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size());
572         for (String schema : schemas) {
573             typePropertyPathsMap.put(schema,
574                     Objects.requireNonNull(
575                             mProjectionTypePropertyMasks.getStringArrayList(schema)));
576         }
577         return typePropertyPathsMap;
578     }
579 
580     /**
581      * Returns a map from schema type to property paths to be used for projection.
582      *
583      * <p>If the map is empty, then all properties will be retrieved for all results.
584      *
585      * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned
586      * by this function, rather than calling it multiple times.
587      *
588      * @return A mapping of schema types to lists of projection {@link PropertyPath} objects.
589      */
getProjectionPaths()590     public @NonNull Map<String, List<PropertyPath>> getProjectionPaths() {
591         Set<String> schemas = mProjectionTypePropertyMasks.keySet();
592         Map<String, List<PropertyPath>> typePropertyPathsMap = new ArrayMap<>(schemas.size());
593         for (String schema : schemas) {
594             ArrayList<String> propertyPathList = mProjectionTypePropertyMasks.getStringArrayList(
595                     schema);
596             if (propertyPathList != null) {
597                 List<PropertyPath> copy = new ArrayList<>(propertyPathList.size());
598                 for (int i = 0; i < propertyPathList.size(); i++) {
599                     String p = propertyPathList.get(i);
600                     copy.add(new PropertyPath(p));
601                 }
602                 typePropertyPathsMap.put(schema, copy);
603             }
604         }
605         return typePropertyPathsMap;
606     }
607 
608     /**
609      * Returns properties weights to be used for scoring.
610      *
611      * <p>Calling this function repeatedly is inefficient. Prefer to retain the {@link Map} returned
612      * by this function, rather than calling it multiple times.
613      *
614      * @return a {@link Map} of schema type to an inner-map of property paths of the schema type to
615      * the weight to set for that property.
616      */
getPropertyWeights()617     public @NonNull Map<String, Map<String, Double>> getPropertyWeights() {
618         Set<String> schemaTypes = mTypePropertyWeightsField.keySet();
619         Map<String, Map<String, Double>> typePropertyWeightsMap = new ArrayMap<>(
620                 schemaTypes.size());
621         for (String schemaType : schemaTypes) {
622             Bundle propertyPathBundle = mTypePropertyWeightsField.getBundle(schemaType);
623             if (propertyPathBundle != null) {
624                 Set<String> propertyPaths = propertyPathBundle.keySet();
625                 Map<String, Double> propertyPathWeights = new ArrayMap<>(propertyPaths.size());
626                 for (String propertyPath : propertyPaths) {
627                     propertyPathWeights.put(propertyPath,
628                             propertyPathBundle.getDouble(propertyPath));
629                 }
630                 typePropertyWeightsMap.put(schemaType, propertyPathWeights);
631             }
632         }
633         return typePropertyWeightsMap;
634     }
635 
636     /**
637      * Returns properties weights to be used for scoring.
638      *
639      * <p>Calling this function repeatedly is inefficient. Prefer to retain the {@link Map} returned
640      * by this function, rather than calling it multiple times.
641      *
642      * @return a {@link Map} of schema type to an inner-map of property paths of the schema type to
643      * the weight to set for that property.
644      */
getPropertyWeightPaths()645     public @NonNull Map<String, Map<PropertyPath, Double>> getPropertyWeightPaths() {
646         Set<String> schemaTypes = mTypePropertyWeightsField.keySet();
647         Map<String, Map<PropertyPath, Double>> typePropertyWeightsMap = new ArrayMap<>(
648                 schemaTypes.size());
649         for (String schemaType : schemaTypes) {
650             Bundle propertyPathBundle = mTypePropertyWeightsField.getBundle(schemaType);
651             if (propertyPathBundle != null) {
652                 Set<String> propertyPaths = propertyPathBundle.keySet();
653                 Map<PropertyPath, Double> propertyPathWeights =
654                         new ArrayMap<>(propertyPaths.size());
655                 for (String propertyPath : propertyPaths) {
656                     propertyPathWeights.put(
657                             new PropertyPath(propertyPath),
658                             propertyPathBundle.getDouble(propertyPath));
659                 }
660                 typePropertyWeightsMap.put(schemaType, propertyPathWeights);
661             }
662         }
663         return typePropertyWeightsMap;
664     }
665 
666     /**
667      * Get the type of grouping limit to apply, or 0 if {@link Builder#setResultGrouping} was not
668      * called.
669      */
670     @GroupingType
getResultGroupingTypeFlags()671     public int getResultGroupingTypeFlags() {
672         return mResultGroupingTypeFlags;
673     }
674 
675     /**
676      * Get the maximum number of results to return for each group.
677      *
678      * @return the maximum number of results to return for each group or 0 if
679      * {@link Builder#setResultGrouping(int, int)} was not called.
680      */
getResultGroupingLimit()681     public int getResultGroupingLimit() {
682         return mGroupingLimit;
683     }
684 
685     /**
686      * Returns specification on which documents need to be joined.
687      */
getJoinSpec()688     public @Nullable JoinSpec getJoinSpec() {
689         return mJoinSpec;
690     }
691 
692     /**
693      * Get the advanced ranking expression, or "" if {@link Builder#setRankingStrategy(String)}
694      * was not called.
695      */
getAdvancedRankingExpression()696     public @NonNull String getAdvancedRankingExpression() {
697         return mAdvancedRankingExpression;
698     }
699 
700 
701     /**
702      * Gets a tag to indicate the source of this search, or {@code null} if
703      * {@link Builder#setSearchSourceLogTag(String)} was not called.
704      *
705      * <p> Some AppSearch implementations may log a hash of this tag using statsd. This tag may be
706      * used for tracing performance issues and crashes to a component of an app.
707      *
708      * <p>Call {@link Builder#setSearchSourceLogTag} and give a unique value if you want to
709      * distinguish this search scenario with other search scenarios during performance analysis.
710      *
711      * <p>Under no circumstances will AppSearch log the raw String value using statsd, but it
712      * will be provided as-is to custom {@code AppSearchLogger} implementations you have
713      * registered in your app.
714      */
715     @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG)
getSearchSourceLogTag()716     public @Nullable String getSearchSourceLogTag() {
717         return mSearchSourceLogTag;
718     }
719 
720     /**
721      * Returns the list of {@link EmbeddingVector} that can be referenced in the query through the
722      * "getEmbeddingParameter({index})" function.
723      *
724      * @see AppSearchSession#search
725      */
726     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
getEmbeddingParameters()727     public @NonNull List<EmbeddingVector> getEmbeddingParameters() {
728         return mEmbeddingParameters;
729     }
730 
731     /**
732      * Returns the default embedding metric type used for embedding search
733      * (see {@link AppSearchSession#search}) and ranking
734      * (see {@link SearchSpec.Builder#setRankingStrategy(String)}).
735      */
736     @EmbeddingSearchMetricType
737     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
getDefaultEmbeddingSearchMetricType()738     public int getDefaultEmbeddingSearchMetricType() {
739         return mDefaultEmbeddingSearchMetricType;
740     }
741 
742     /**
743      * Returns the informational ranking expressions.
744      *
745      * @see Builder#addInformationalRankingExpressions
746      */
747     @FlaggedApi(Flags.FLAG_ENABLE_INFORMATIONAL_RANKING_EXPRESSIONS)
getInformationalRankingExpressions()748     public @NonNull List<String> getInformationalRankingExpressions() {
749         return mInformationalRankingExpressions;
750     }
751 
752     /**
753      * Returns the list of String parameters that can be referenced in the query through the
754      * "getSearchStringParameter({index})" function.
755      *
756      * @see AppSearchSession#search
757      */
758     @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
getSearchStringParameters()759     public @NonNull List<String> getSearchStringParameters() {
760         return mSearchStringParameters;
761     }
762 
763     /**
764      * Returns whether the NUMERIC_SEARCH feature is enabled.
765      */
isNumericSearchEnabled()766     public boolean isNumericSearchEnabled() {
767         return mEnabledFeatures.contains(FeatureConstants.NUMERIC_SEARCH);
768     }
769 
770     /**
771      * Returns whether the VERBATIM_SEARCH feature is enabled.
772      */
isVerbatimSearchEnabled()773     public boolean isVerbatimSearchEnabled() {
774         return mEnabledFeatures.contains(FeatureConstants.VERBATIM_SEARCH);
775     }
776 
777     /**
778      * Returns whether the LIST_FILTER_QUERY_LANGUAGE feature is enabled.
779      */
isListFilterQueryLanguageEnabled()780     public boolean isListFilterQueryLanguageEnabled() {
781         return mEnabledFeatures.contains(FeatureConstants.LIST_FILTER_QUERY_LANGUAGE);
782     }
783 
784     /**
785      * Returns whether the ScorablePropertyRanking feature is enabled.
786      */
787     @ExperimentalAppSearchApi
788     @FlaggedApi(Flags.FLAG_ENABLE_SCORABLE_PROPERTY)
isScorablePropertyRankingEnabled()789     public boolean isScorablePropertyRankingEnabled() {
790         return mEnabledFeatures.contains(FeatureConstants.SCHEMA_SCORABLE_PROPERTY_CONFIG);
791     }
792 
793     /**
794      * Returns whether the LIST_FILTER_HAS_PROPERTY_FUNCTION feature is enabled.
795      */
796     @FlaggedApi(Flags.FLAG_ENABLE_LIST_FILTER_HAS_PROPERTY_FUNCTION)
isListFilterHasPropertyFunctionEnabled()797     public boolean isListFilterHasPropertyFunctionEnabled() {
798         return mEnabledFeatures.contains(FeatureConstants.LIST_FILTER_HAS_PROPERTY_FUNCTION);
799     }
800 
801     /**
802      * Returns whether the LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION feature is enabled.
803      */
804     @ExperimentalAppSearchApi
805     @FlaggedApi(Flags.FLAG_ENABLE_LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION)
isListFilterMatchScoreExpressionFunctionEnabled()806     public boolean isListFilterMatchScoreExpressionFunctionEnabled() {
807         return mEnabledFeatures.contains(
808                 FeatureConstants.LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION);
809     }
810 
811     /**
812      * Get the list of enabled features that the caller is intending to use in this search call.
813      *
814      * @return the set of {@link Features} enabled in this {@link SearchSpec} Entry.
815      * @exportToFramework:hide
816      */
817     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
getEnabledFeatures()818     public @NonNull List<String> getEnabledFeatures() {
819         return mEnabledFeatures;
820     }
821 
822     @Override
823     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
824     @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2)
writeToParcel(@onNull Parcel dest, int flags)825     public void writeToParcel(@NonNull Parcel dest, int flags) {
826         SearchSpecCreator.writeToParcel(this, dest, flags);
827     }
828 
829     /** Builder for {@link SearchSpec objects}. */
830     public static final class Builder {
831         private List<String> mSchemas = new ArrayList<>();
832         private List<String> mNamespaces = new ArrayList<>();
833         private Bundle mTypePropertyFilters = new Bundle();
834         private List<String> mPackageNames = new ArrayList<>();
835         private ArraySet<String> mEnabledFeatures = new ArraySet<>();
836         private Bundle mProjectionTypePropertyMasks = new Bundle();
837         private Bundle mTypePropertyWeights = new Bundle();
838         private List<EmbeddingVector> mEmbeddingParameters = new ArrayList<>();
839         private List<String> mSearchStringParameters = new ArrayList<>();
840         private List<String> mFilterDocumentIds = new ArrayList<>();
841 
842         private int mResultCountPerPage = DEFAULT_NUM_PER_PAGE;
843         @TermMatch private int mTermMatchType = TERM_MATCH_PREFIX;
844         @EmbeddingSearchMetricType
845         private int mDefaultEmbeddingSearchMetricType = EMBEDDING_SEARCH_METRIC_TYPE_COSINE;
846         private int mSnippetCount = 0;
847         private int mSnippetCountPerProperty = MAX_SNIPPET_PER_PROPERTY_COUNT;
848         private int mMaxSnippetSize = 0;
849         @RankingStrategy private int mRankingStrategy = RANKING_STRATEGY_NONE;
850         @Order private int mOrder = ORDER_DESCENDING;
851         @GroupingType private int mGroupingTypeFlags = 0;
852         private int mGroupingLimit = 0;
853         private @Nullable JoinSpec mJoinSpec;
854         private String mAdvancedRankingExpression = "";
855         private List<String> mInformationalRankingExpressions = new ArrayList<>();
856         private @Nullable String mSearchSourceLogTag;
857         private boolean mRetrieveEmbeddingMatchInfos = false;
858         private boolean mBuilt = false;
859 
860         /** Constructs a new {@link Builder} for {@link SearchSpec} objects. */
Builder()861         public Builder() {
862         }
863 
864         /** Constructs a new {@link Builder} from the given {@link SearchSpec}. */
865         @ExperimentalAppSearchApi
866         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
867         @OptIn(markerClass = ExperimentalAppSearchApi.class)
Builder(@onNull SearchSpec searchSpec)868         public Builder(@NonNull SearchSpec searchSpec) {
869             Objects.requireNonNull(searchSpec);
870             mSchemas = new ArrayList<>(searchSpec.getFilterSchemas());
871             mNamespaces = new ArrayList<>(searchSpec.getFilterNamespaces());
872             for (Map.Entry<String, List<String>> entry :
873                     searchSpec.getFilterProperties().entrySet()) {
874                 addFilterProperties(entry.getKey(), entry.getValue());
875             }
876             mPackageNames = new ArrayList<>(searchSpec.getFilterPackageNames());
877             mEnabledFeatures = new ArraySet<>(searchSpec.getEnabledFeatures());
878             for (Map.Entry<String, List<String>> entry : searchSpec.getProjections().entrySet()) {
879                 addProjection(entry.getKey(), entry.getValue());
880             }
881             for (Map.Entry<String, Map<String, Double>> entry :
882                     searchSpec.getPropertyWeights().entrySet()) {
883                 setPropertyWeights(entry.getKey(), entry.getValue());
884             }
885             mEmbeddingParameters = new ArrayList<>(searchSpec.getEmbeddingParameters());
886             mSearchStringParameters = new ArrayList<>(searchSpec.getSearchStringParameters());
887             mResultCountPerPage = searchSpec.getResultCountPerPage();
888             mTermMatchType = searchSpec.getTermMatch();
889             mDefaultEmbeddingSearchMetricType = searchSpec.getDefaultEmbeddingSearchMetricType();
890             mSnippetCount = searchSpec.getSnippetCount();
891             mSnippetCountPerProperty = searchSpec.getSnippetCountPerProperty();
892             mMaxSnippetSize = searchSpec.getMaxSnippetSize();
893             mRankingStrategy = searchSpec.getRankingStrategy();
894             mOrder = searchSpec.getOrder();
895             mGroupingTypeFlags = searchSpec.getResultGroupingTypeFlags();
896             mGroupingLimit = searchSpec.getResultGroupingLimit();
897             mJoinSpec = searchSpec.getJoinSpec();
898             mAdvancedRankingExpression = searchSpec.getAdvancedRankingExpression();
899             mInformationalRankingExpressions = new ArrayList<>(
900                     searchSpec.getInformationalRankingExpressions());
901             mSearchSourceLogTag = searchSpec.getSearchSourceLogTag();
902             mFilterDocumentIds = new ArrayList<>(searchSpec.getFilterDocumentIds());
903             mRetrieveEmbeddingMatchInfos = searchSpec.shouldRetrieveEmbeddingMatchInfos();
904         }
905 
906         /**
907          * Sets how the query terms should match {@code TermMatchCode} in the index.
908          *
909          * <p>If this method is not called, the default term match type is
910          * {@link SearchSpec#TERM_MATCH_PREFIX}.
911          */
912         @CanIgnoreReturnValue
setTermMatch(@ermMatch int termMatchType)913         public @NonNull Builder setTermMatch(@TermMatch int termMatchType) {
914             Preconditions.checkArgumentInRange(termMatchType, TERM_MATCH_EXACT_ONLY,
915                     TERM_MATCH_PREFIX, "Term match type");
916             resetIfBuilt();
917             mTermMatchType = termMatchType;
918             return this;
919         }
920 
921         /**
922          * Adds a Schema type filter to {@link SearchSpec} Entry. Only search for documents that
923          * have the specified schema types.
924          *
925          * <p>If unset, the query will search over all schema types.
926          */
927         @CanIgnoreReturnValue
addFilterSchemas(@onNull String... schemas)928         public @NonNull Builder addFilterSchemas(@NonNull String... schemas) {
929             Preconditions.checkNotNull(schemas);
930             resetIfBuilt();
931             return addFilterSchemas(Arrays.asList(schemas));
932         }
933 
934         /**
935          * Adds a Schema type filter to {@link SearchSpec} Entry. Only search for documents that
936          * have the specified schema types.
937          *
938          * <p>If unset, the query will search over all schema types.
939          */
940         @CanIgnoreReturnValue
addFilterSchemas(@onNull Collection<String> schemas)941         public @NonNull Builder addFilterSchemas(@NonNull Collection<String> schemas) {
942             Preconditions.checkNotNull(schemas);
943             resetIfBuilt();
944             mSchemas.addAll(schemas);
945             return this;
946         }
947 
948 // @exportToFramework:startStrip()
949 
950         /**
951          * Adds the Schema names of given document classes to the Schema type filter of
952          * {@link SearchSpec} Entry. Only search for documents that have the specified schema types.
953          *
954          * <p>If unset, the query will search over all schema types.
955          *
956          * <p>Merged list available from {@link #getFilterSchemas()}.
957          *
958          * @param documentClasses classes annotated with {@link Document}.
959          */
960         @CanIgnoreReturnValue
961         @SuppressLint("MissingGetterMatchingBuilder")
addFilterDocumentClasses( @onNull Collection<? extends java.lang.Class<?>> documentClasses)962         public @NonNull Builder addFilterDocumentClasses(
963                 @NonNull Collection<? extends java.lang.Class<?>> documentClasses)
964                 throws AppSearchException {
965             Preconditions.checkNotNull(documentClasses);
966             resetIfBuilt();
967             List<String> schemas = new ArrayList<>(documentClasses.size());
968             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
969             for (java.lang.Class<?> documentClass : documentClasses) {
970                 DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
971                 schemas.add(factory.getSchemaName());
972             }
973             addFilterSchemas(schemas);
974             return this;
975         }
976 // @exportToFramework:endStrip()
977 
978 // @exportToFramework:startStrip()
979 
980         /**
981          * Adds the Schema names of given document classes to the Schema type filter of
982          * {@link SearchSpec} Entry. Only search for documents that have the specified schema types.
983          *
984          * <p>If unset, the query will search over all schema types.
985          *
986          * <p>Merged list available from {@link #getFilterSchemas()}.
987          *
988          * @param documentClasses classes annotated with {@link Document}.
989          */
990         @CanIgnoreReturnValue
991         @SuppressLint("MissingGetterMatchingBuilder")
addFilterDocumentClasses( @onNull java.lang.Class<?>.... documentClasses)992         public @NonNull Builder addFilterDocumentClasses(
993                 @NonNull java.lang.Class<?>... documentClasses) throws AppSearchException {
994             Preconditions.checkNotNull(documentClasses);
995             resetIfBuilt();
996             return addFilterDocumentClasses(Arrays.asList(documentClasses));
997         }
998 // @exportToFramework:endStrip()
999 
1000         /** Clears all schema type filters. */
1001         @ExperimentalAppSearchApi
1002         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1003         @CanIgnoreReturnValue
clearFilterSchemas()1004         public @NonNull Builder clearFilterSchemas() {
1005             resetIfBuilt();
1006             mSchemas.clear();
1007             return this;
1008         }
1009 
1010         /**
1011          * Adds property paths for the specified type to the property filter of
1012          * {@link SearchSpec} Entry. Only returns documents that have matches under
1013          * the specified properties. If property paths are added for a type, then only the
1014          * properties referred to will be searched for results of that type.
1015          *
1016          * <p> If a property path that is specified isn't present in a result, it will be ignored
1017          * for that result. Property paths cannot be null.
1018          *
1019          * <p>If no property paths are added for a particular type, then all properties of
1020          * results of that type will be searched.
1021          *
1022          * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc.
1023          *
1024          * <p>If property paths are added for the
1025          * {@link SearchSpec#SCHEMA_TYPE_WILDCARD}, then those property paths will
1026          * apply to all results, excepting any types that have their own, specific property paths
1027          * set.
1028          *
1029          * @param schema the {@link AppSearchSchema} that contains the target properties
1030          * @param propertyPaths The String version of {@link PropertyPath}. A dot-delimited
1031          *                      sequence of property names.
1032          */
1033         @CanIgnoreReturnValue
1034         @RequiresFeature(
1035                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1036                 name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES)
1037         @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_PROPERTIES)
addFilterProperties(@onNull String schema, @NonNull Collection<String> propertyPaths)1038         public @NonNull Builder addFilterProperties(@NonNull String schema,
1039                 @NonNull Collection<String> propertyPaths) {
1040             Preconditions.checkNotNull(schema);
1041             Preconditions.checkNotNull(propertyPaths);
1042             resetIfBuilt();
1043             ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
1044             for (String propertyPath : propertyPaths) {
1045                 Preconditions.checkNotNull(propertyPath);
1046                 propertyPathsArrayList.add(propertyPath);
1047             }
1048             mTypePropertyFilters.putStringArrayList(schema, propertyPathsArrayList);
1049             return this;
1050         }
1051 
1052         /**
1053          * Adds property paths for the specified type to the property filter of
1054          * {@link SearchSpec} Entry. Only returns documents that have matches under the specified
1055          * properties. If property paths are added for a type, then only the properties referred
1056          * to will be searched for results of that type.
1057          *
1058          * @see #addFilterProperties(String, Collection)
1059          *
1060          * @param schema the {@link AppSearchSchema} that contains the target properties
1061          * @param propertyPaths The {@link PropertyPath} to search search over
1062          */
1063         // Getter method is getFilterProperties
1064         @SuppressLint("MissingGetterMatchingBuilder")
1065         @RequiresFeature(
1066                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1067                 name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES)
1068         @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_PROPERTIES)
addFilterPropertyPaths(@onNull String schema, @NonNull Collection<PropertyPath> propertyPaths)1069         public @NonNull Builder addFilterPropertyPaths(@NonNull String schema,
1070                 @NonNull Collection<PropertyPath> propertyPaths) {
1071             Preconditions.checkNotNull(schema);
1072             Preconditions.checkNotNull(propertyPaths);
1073             ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
1074             for (PropertyPath propertyPath : propertyPaths) {
1075                 propertyPathsArrayList.add(propertyPath.toString());
1076             }
1077             return addFilterProperties(schema, propertyPathsArrayList);
1078         }
1079 
1080 
1081 // @exportToFramework:startStrip()
1082 
1083         /**
1084          * Adds property paths for the specified type to the property filter of
1085          * {@link SearchSpec} Entry. Only returns documents that have matches under the specified
1086          * properties. If property paths are added for a type, then only the properties referred
1087          * to will be searched for results of that type.
1088          *
1089          * @see #addFilterProperties(String, Collection)
1090          *
1091          * @param documentClass class annotated with {@link Document}.
1092          * @param propertyPaths The String version of {@link PropertyPath}. A dot-delimited
1093                                 sequence of property names.
1094          *
1095          */
1096         @RequiresFeature(
1097                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1098                 name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES)
addFilterProperties(@onNull java.lang.Class<?> documentClass, @NonNull Collection<String> propertyPaths)1099         public @NonNull Builder addFilterProperties(@NonNull java.lang.Class<?> documentClass,
1100                 @NonNull Collection<String> propertyPaths) throws AppSearchException {
1101             Preconditions.checkNotNull(documentClass);
1102             Preconditions.checkNotNull(propertyPaths);
1103             resetIfBuilt();
1104             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
1105             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
1106             return addFilterProperties(factory.getSchemaName(), propertyPaths);
1107         }
1108 // @exportToFramework:endStrip()
1109 
1110 // @exportToFramework:startStrip()
1111         /**
1112          * Adds property paths for the specified type to the property filter of
1113          * {@link SearchSpec} Entry. Only returns documents that have matches under the specified
1114          * properties. If property paths are added for a type, then only the properties referred
1115          * to will be searched for results of that type.
1116          *
1117          * @see #addFilterProperties(String, Collection)
1118          *
1119          * @param documentClass class annotated with {@link Document}.
1120          * @param propertyPaths The {@link PropertyPath} to search search over
1121          *
1122          */
1123         // Getter method is getFilterProperties
1124         @SuppressLint("MissingGetterMatchingBuilder")
1125         @RequiresFeature(
1126                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1127                 name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES)
addFilterPropertyPaths(@onNull java.lang.Class<?> documentClass, @NonNull Collection<PropertyPath> propertyPaths)1128         public @NonNull Builder addFilterPropertyPaths(@NonNull java.lang.Class<?> documentClass,
1129                 @NonNull Collection<PropertyPath> propertyPaths) throws AppSearchException {
1130             Preconditions.checkNotNull(documentClass);
1131             Preconditions.checkNotNull(propertyPaths);
1132             resetIfBuilt();
1133             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
1134             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
1135             return addFilterPropertyPaths(factory.getSchemaName(), propertyPaths);
1136         }
1137 // @exportToFramework:endStrip()
1138 
1139         /** Clears the property filters for all schema types. */
1140         @ExperimentalAppSearchApi
1141         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1142         @CanIgnoreReturnValue
clearFilterProperties()1143         public @NonNull Builder clearFilterProperties() {
1144             resetIfBuilt();
1145             mTypePropertyFilters.clear();
1146             return this;
1147         }
1148 
1149         /**
1150          * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that
1151          * have the specified namespaces.
1152          * <p>If unset, the query will search over all namespaces.
1153          */
1154         @CanIgnoreReturnValue
addFilterNamespaces(@onNull String... namespaces)1155         public @NonNull Builder addFilterNamespaces(@NonNull String... namespaces) {
1156             Preconditions.checkNotNull(namespaces);
1157             resetIfBuilt();
1158             return addFilterNamespaces(Arrays.asList(namespaces));
1159         }
1160 
1161         /**
1162          * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that
1163          * have the specified namespaces.
1164          * <p>If unset, the query will search over all namespaces.
1165          */
1166         @CanIgnoreReturnValue
addFilterNamespaces(@onNull Collection<String> namespaces)1167         public @NonNull Builder addFilterNamespaces(@NonNull Collection<String> namespaces) {
1168             Preconditions.checkNotNull(namespaces);
1169             resetIfBuilt();
1170             mNamespaces.addAll(namespaces);
1171             return this;
1172         }
1173 
1174         /** Clears all namespace filters. */
1175         @ExperimentalAppSearchApi
1176         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1177         @CanIgnoreReturnValue
clearFilterNamespaces()1178         public @NonNull Builder clearFilterNamespaces() {
1179             resetIfBuilt();
1180             mNamespaces.clear();
1181             return this;
1182         }
1183 
1184         /**
1185          * Adds a package name filter to {@link SearchSpec} Entry. Only search for documents that
1186          * were indexed from the specified packages.
1187          *
1188          * <p>If unset, the query will search over all packages that the caller has access to.
1189          * If package names are specified which caller doesn't have access to, then those package
1190          * names will be ignored.
1191          */
1192         @CanIgnoreReturnValue
addFilterPackageNames(@onNull String... packageNames)1193         public @NonNull Builder addFilterPackageNames(@NonNull String... packageNames) {
1194             Preconditions.checkNotNull(packageNames);
1195             resetIfBuilt();
1196             return addFilterPackageNames(Arrays.asList(packageNames));
1197         }
1198 
1199         /**
1200          * Adds a package name filter to {@link SearchSpec} Entry. Only search for documents that
1201          * were indexed from the specified packages.
1202          *
1203          * <p>If unset, the query will search over all packages that the caller has access to.
1204          * If package names are specified which caller doesn't have access to, then those package
1205          * names will be ignored.
1206          */
1207         @CanIgnoreReturnValue
addFilterPackageNames(@onNull Collection<String> packageNames)1208         public @NonNull Builder addFilterPackageNames(@NonNull Collection<String> packageNames) {
1209             Preconditions.checkNotNull(packageNames);
1210             resetIfBuilt();
1211             mPackageNames.addAll(packageNames);
1212             return this;
1213         }
1214 
1215         /** Clears all package name filters. */
1216         @ExperimentalAppSearchApi
1217         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1218         @CanIgnoreReturnValue
clearFilterPackageNames()1219         public @NonNull Builder clearFilterPackageNames() {
1220             resetIfBuilt();
1221             mPackageNames.clear();
1222             return this;
1223         }
1224 
1225         /**
1226          * Adds a document id filter to {@link SearchSpec} Entry. Only search for documents that
1227          * have the specified document ids.
1228          *
1229          * <p>If unset, the query will search over all documents.
1230          */
1231         @CanIgnoreReturnValue
1232         @ExperimentalAppSearchApi
1233         @RequiresFeature(
1234                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1235                 name = Features.SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS)
1236         @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_DOCUMENT_IDS)
addFilterDocumentIds(@onNull String... documentIds)1237         public @NonNull Builder addFilterDocumentIds(@NonNull String... documentIds) {
1238             Preconditions.checkNotNull(documentIds);
1239             resetIfBuilt();
1240             return addFilterDocumentIds(Arrays.asList(documentIds));
1241         }
1242 
1243         /**
1244          * Adds a document id filter to {@link SearchSpec} Entry. Only search for documents that
1245          * have the specified document ids.
1246          *
1247          * <p>If unset, the query will search over all documents.
1248          */
1249         @CanIgnoreReturnValue
1250         @ExperimentalAppSearchApi
1251         @RequiresFeature(
1252                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1253                 name = Features.SEARCH_SPEC_ADD_FILTER_DOCUMENT_IDS)
1254         @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_DOCUMENT_IDS)
addFilterDocumentIds(@onNull Collection<String> documentIds)1255         public @NonNull Builder addFilterDocumentIds(@NonNull Collection<String> documentIds) {
1256             Preconditions.checkNotNull(documentIds);
1257             resetIfBuilt();
1258             mFilterDocumentIds.addAll(documentIds);
1259             return this;
1260         }
1261 
1262         /** Clears the document id filters. */
1263         @ExperimentalAppSearchApi
1264         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1265         @CanIgnoreReturnValue
clearFilterDocumentIds()1266         public @NonNull Builder clearFilterDocumentIds() {
1267             resetIfBuilt();
1268             mFilterDocumentIds.clear();
1269             return this;
1270         }
1271 
1272         /**
1273          * Sets the number of results per page in the returned object.
1274          *
1275          * <p>The default number of results per page is 10.
1276          */
1277         @CanIgnoreReturnValue
setResultCountPerPage( @ntRangefrom = 0, to = MAX_NUM_PER_PAGE) int resultCountPerPage)1278         public @NonNull SearchSpec.Builder setResultCountPerPage(
1279                 @IntRange(from = 0, to = MAX_NUM_PER_PAGE) int resultCountPerPage) {
1280             Preconditions.checkArgumentInRange(
1281                     resultCountPerPage, 0, MAX_NUM_PER_PAGE, "resultCountPerPage");
1282             resetIfBuilt();
1283             mResultCountPerPage = resultCountPerPage;
1284             return this;
1285         }
1286 
1287         /** Sets ranking strategy for AppSearch results. */
1288         @CanIgnoreReturnValue
setRankingStrategy(@ankingStrategy int rankingStrategy)1289         public @NonNull Builder setRankingStrategy(@RankingStrategy int rankingStrategy) {
1290             Preconditions.checkArgumentInRange(rankingStrategy, RANKING_STRATEGY_NONE,
1291                     RANKING_STRATEGY_JOIN_AGGREGATE_SCORE, "Result ranking strategy");
1292             resetIfBuilt();
1293             mRankingStrategy = rankingStrategy;
1294             mAdvancedRankingExpression = "";
1295             return this;
1296         }
1297 
1298         /**
1299          * Enables advanced ranking to score based on {@code advancedRankingExpression}.
1300          *
1301          * <p>This method will set RankingStrategy to
1302          * {@link #RANKING_STRATEGY_ADVANCED_RANKING_EXPRESSION}.
1303          *
1304          * <p>The ranking expression is a mathematical expression that will be evaluated to a
1305          * floating-point number of double type representing the score of each document.
1306          *
1307          * <p>Numeric literals, arithmetic operators, mathematical functions, document-based, and
1308          * property-value-based functions are supported to build expressions.
1309          *
1310          * <p>The following are supported arithmetic operators:
1311          * <ul>
1312          *     <li>Addition(+)
1313          *     <li>Subtraction(-)
1314          *     <li>Multiplication(*)
1315          *     <li>Floating Point Division(/)
1316          * </ul>
1317          *
1318          * <p>Operator precedences are compliant with the Java Language, and parentheses are
1319          * supported. For example, "2.2 + (3 - 4) / 2" evaluates to 1.7.
1320          *
1321          * <p>The following are supported basic mathematical functions:
1322          * <ul>
1323          *     <li>log(x) - the natural log of x
1324          *     <li>log(x, y) - the log of y with base x
1325          *     <li>pow(x, y) - x to the power of y
1326          *     <li>sqrt(x)
1327          *     <li>abs(x)
1328          *     <li>sin(x), cos(x), tan(x)
1329          *     <li>Example: "max(abs(-100), 10) + pow(2, 10)" will be evaluated to 1124
1330          * </ul>
1331          *
1332          * <p>The following variadic mathematical functions are supported, with n > 0. They also
1333          * accept list value parameters. For example, if V is a value of list type, we can call
1334          * sum(V) to get the sum of all the values in V. List literals are not supported, so a
1335          * value of list type can only be constructed as a return value of some particular
1336          * document-based functions.
1337          * <ul>
1338          *     <li>max(v1, v2, ..., vn) or max(V)
1339          *     <li>min(v1, v2, ..., vn) or min(V)
1340          *     <li>len(v1, v2, ..., vn) or len(V)
1341          *     <li>sum(v1, v2, ..., vn) or sum(V)
1342          *     <li>avg(v1, v2, ..., vn) or avg(V)
1343          * </ul>
1344          *
1345          * <p>Document-based functions must be called via "this", which represents the current
1346          * document being scored. The following are supported document-based functions:
1347          * <ul>
1348          *     <li>this.documentScore()
1349          *     <p>Get the app-provided document score of the current document. This is the same
1350          *     score that is returned for {@link #RANKING_STRATEGY_DOCUMENT_SCORE}.
1351          *     <li>this.creationTimestamp()
1352          *     <p>Get the creation timestamp of the current document. This is the same score that
1353          *     is returned for {@link #RANKING_STRATEGY_CREATION_TIMESTAMP}.
1354          *     <li>this.relevanceScore()
1355          *     <p>Get the BM25F relevance score of the current document in relation to the query
1356          *     string. This is the same score that is returned for
1357          *     {@link #RANKING_STRATEGY_RELEVANCE_SCORE}.
1358          *     <li>this.usageCount(type) and this.usageLastUsedTimestamp(type)
1359          *     <p>Get the number of usages or the timestamp of last usage by type for the current
1360          *     document, where type must be evaluated to an integer from 1 to 2. Type 1 refers to
1361          *     usages reported by {@link AppSearchSession#reportUsageAsync}, and type 2 refers to
1362          *     usages reported by {@link GlobalSearchSession#reportSystemUsageAsync}.
1363          *     <li>this.childrenRankingSignals()
1364          *     <p>Returns a list of children ranking signals calculated by scoring the joined
1365          *     documents using the ranking strategy specified in the nested {@link SearchSpec}.
1366          *     Currently, a document can only be a child of another document in the context of
1367          *     joins. If this function is called without the Join API enabled, a type error will
1368          *     be raised.
1369          *     <li>this.propertyWeights()
1370          *     <p>Returns a list of the normalized weights of the matched properties for the
1371          *     current document being scored. Property weights come from what's specified in
1372          *     {@link SearchSpec}. After normalizing, each provided weight will be divided by the
1373          *     maximum weight, so that each of them will be <= 1.
1374          *     <li>this.matchedSemanticScores(getEmbeddingParameter({embedding_index}), {metric})
1375          *     <p>Returns a list of the matched similarity scores from "semanticSearch" in the query
1376          *     expression (see also {@link AppSearchSession#search}) based on embedding_index and
1377          *     metric. If metric is omitted, it defaults to the metric specified in
1378          *     {@link SearchSpec.Builder#setDefaultEmbeddingSearchMetricType(int)}. If no
1379          *     "semanticSearch" is called for embedding_index and metric in the query, this
1380          *     function will return an empty list. If multiple "semanticSearch"s are called for
1381          *     the same embedding_index and metric, this function will return a list of their
1382          *     merged scores.
1383          *     <p>Example: `this.matchedSemanticScores(getEmbeddingParameter(0), "COSINE")` will
1384          *     return a list of matched scores within the range of [0.5, 1], if
1385          *     `semanticSearch(getEmbeddingParameter(0), 0.5, 1, "COSINE")` is called in the
1386          *     query expression.
1387          * </ul>
1388          *
1389          * <p>Property-value-based functions can be called via the function of
1390          * getScorableProperty(schemaType, propertyPath)
1391          *
1392          * <ul>
1393          *     <li>In order to use this function, ScorablePropertyRanking feature must be enabled
1394          *     via {@link SearchSpec.Builder#setScorablePropertyRankingEnabled(boolean)}.
1395          *     <li>Param 'schemaType' must be a valid AppSearch SchemaType otherwise an error is
1396          *     returned.
1397          *     <li>Param 'propertyPath' must be valid and scorable otherwise an error is returned.
1398          *     It is considered scorable when:
1399          *     <ul>
1400          *         <li>It is to a property that is set to be enabled for scoring, or that
1401          *         </li>
1402          *         <li>
1403          *             It points to a scorable property of nested schema types.
1404          *         </li>
1405          *     </ul>
1406          *     <li>This function returns a list double values for the matched documents.
1407          *     <ul>
1408          *         <li>If the matched document's schema is different from 'schemaType', or the
1409          *         property under the 'propertyPath' holds no element, an empty list is returned.
1410          *         </li>
1411          *     </ul>
1412          *     <li>Some examples below:
1413          *     <p>Suppose that there are two schemas: 'Gmail' and 'Person'. 'Gmail' schema has a
1414          *     property 'recipient' with schema type 'Person'.
1415          *     In the advanced ranking expression, you can have:
1416          *     <ul>
1417          *         <li> "sum(getScorableProperty('Gmail', 'viewTimes'))"
1418          *         <li> "maxOrDefault(getScorableProperty('Person', 'income'), 0)"
1419          *         <li> "sum(getScorableProperty('Gmail', 'recipient.income'))"
1420          *         <li> "this.documentScore() + sum(getScorableProperty('Gmail', 'viewTimes'))"
1421          *     </ul>
1422          * </ul>
1423          *
1424          * <p>The following functions are provided for enhanced list manipulation.
1425          * <ul>
1426          *   <li>minOrDefault(V, default_score)
1427          *     <p>Returns the minimum value in the input list V or the default_score if the list is
1428          *     empty.
1429          *     <p>Example: "minOrDefault(this.matchedSemanticScores(getEmbeddingParameter(0)), 10)"
1430          *     will return the minimum matched semantic scores or 10 if there is no matched score
1431          *     for the current document.
1432          *     <p>This function requires the feature
1433          *     {@link Features#SEARCH_SPEC_RANKING_FUNCTION_MAX_MIN_OR_DEFAULT}.
1434          *   <li>maxOrDefault(V, default_score)
1435          *     <p>Returns the maximum value in the input list V or the default_score if the list is
1436          *     empty.
1437          *     <p>Example: "maxOrDefault(this.matchedSemanticScores(getEmbeddingParameter(0)), -10)"
1438          *     will return the maximum matched semantic scores or -10 if there is no matched score
1439          *     for the current document.
1440          *     <p>This function requires the feature
1441          *     {@link Features#SEARCH_SPEC_RANKING_FUNCTION_MAX_MIN_OR_DEFAULT}.
1442          *   <li>filterByRange(V, low, high)
1443          *     <p>Returns a sublist of V that only contains the elements that fall within the
1444          *     specified range [low, high].
1445          *     <p>Example: "filterByRange(this.matchedSemanticScores(getEmbeddingParameter(0)),
1446          *     0, 1)" will return a list of matched semantic scores that are between 0 and 1,
1447          *     inclusive.
1448          *     <p>This function requires the feature
1449          *     {@link Features#SEARCH_SPEC_RANKING_FUNCTION_FILTER_BY_RANGE}.
1450          * </ul>
1451          *
1452          * <p>Some errors may occur when using advanced ranking.
1453          *
1454          * <p>Syntax Error: the expression violates the syntax of the advanced ranking language.
1455          * Below are some examples.
1456          * <ul>
1457          *     <li>"1 + " - missing operand
1458          *     <li>"2 * (1 + 2))" - unbalanced parenthesis
1459          *     <li>"2 ^ 3" - unknown operator
1460          * </ul>
1461          *
1462          * <p>Type Error: the expression fails a static type check. Below are some examples.
1463          * <ul>
1464          *     <li>"sin(2, 3)" - wrong number of arguments for the sin function
1465          *     <li>"this.childrenRankingSignals() + 1" - cannot add a list with a number
1466          *     <li>"this.propertyWeights()" - the final type of the overall expression cannot be
1467          *     a list, which can be fixed by "max(this.propertyWeights())"
1468          *     <li>"abs(this.propertyWeights())" - the abs function does not support list type
1469          *     arguments
1470          *     <li>"print(2)" - unknown function
1471          * </ul>
1472          *
1473          * <p>Evaluation Error: an error occurred while evaluating the value of the expression.
1474          * Below are some examples.
1475          * <ul>
1476          *     <li>"1 / 0", "log(0)", "1 + sqrt(-1)" - getting a non-finite value in the middle
1477          *     of evaluation
1478          *     <li>"this.usageCount(1 + 0.5)" - expect the argument to be an integer. Note that
1479          *     this is not a type error and "this.usageCount(1.5 + 1/2)" can succeed without any
1480          *     issues
1481          *     <li>"this.documentScore()" - in case of an IO error, this will be an evaluation error
1482          * </ul>
1483          *
1484          * <p>Syntax errors and type errors will fail the entire search and will cause
1485          * {@link SearchResults#getNextPageAsync} to throw an {@link AppSearchException} with the
1486          * result code of {@link AppSearchResult#RESULT_INVALID_ARGUMENT}.
1487          * <p>Evaluation errors will result in the offending documents receiving the default score.
1488          * For {@link #ORDER_DESCENDING}, the default score will be 0, for
1489          * {@link #ORDER_ASCENDING} the default score will be infinity.
1490          *
1491          * @param advancedRankingExpression a non-empty string representing the ranking expression.
1492          */
1493         @CanIgnoreReturnValue
1494         @RequiresFeature(
1495                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1496                 name = Features.SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION)
setRankingStrategy(@onNull String advancedRankingExpression)1497         public @NonNull Builder setRankingStrategy(@NonNull String advancedRankingExpression) {
1498             Preconditions.checkStringNotEmpty(advancedRankingExpression);
1499             resetIfBuilt();
1500             mRankingStrategy = RANKING_STRATEGY_ADVANCED_RANKING_EXPRESSION;
1501             mAdvancedRankingExpression = advancedRankingExpression;
1502             return this;
1503         }
1504 
1505         /**
1506          * Adds informational ranking expressions to be evaluated for each document in the search
1507          * result. The values of these expressions will be returned to the caller via
1508          * {@link SearchResult#getInformationalRankingSignals()}. These expressions are purely for
1509          * the caller to retrieve additional information about the result and have no effect on
1510          * ranking.
1511          *
1512          * <p>The syntax is exactly the same as specified in
1513          * {@link SearchSpec.Builder#setRankingStrategy(String)}.
1514          */
1515         @CanIgnoreReturnValue
1516         @RequiresFeature(
1517                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1518                 name = Features.SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS)
1519         @FlaggedApi(Flags.FLAG_ENABLE_INFORMATIONAL_RANKING_EXPRESSIONS)
addInformationalRankingExpressions( @onNull String... informationalRankingExpressions)1520         public @NonNull Builder addInformationalRankingExpressions(
1521                 @NonNull String... informationalRankingExpressions) {
1522             Preconditions.checkNotNull(informationalRankingExpressions);
1523             resetIfBuilt();
1524             return addInformationalRankingExpressions(
1525                     Arrays.asList(informationalRankingExpressions));
1526         }
1527 
1528         /**
1529          * Adds informational ranking expressions to be evaluated for each document in the search
1530          * result. The values of these expressions will be returned to the caller via
1531          * {@link SearchResult#getInformationalRankingSignals()}. These expressions are purely for
1532          * the caller to retrieve additional information about the result and have no effect on
1533          * ranking.
1534          *
1535          * <p>The syntax is exactly the same as specified in
1536          * {@link SearchSpec.Builder#setRankingStrategy(String)}.
1537          */
1538         @CanIgnoreReturnValue
1539         @RequiresFeature(
1540                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1541                 name = Features.SEARCH_SPEC_ADD_INFORMATIONAL_RANKING_EXPRESSIONS)
1542         @FlaggedApi(Flags.FLAG_ENABLE_INFORMATIONAL_RANKING_EXPRESSIONS)
addInformationalRankingExpressions( @onNull Collection<String> informationalRankingExpressions)1543         public @NonNull Builder addInformationalRankingExpressions(
1544                 @NonNull Collection<String> informationalRankingExpressions) {
1545             Preconditions.checkNotNull(informationalRankingExpressions);
1546             resetIfBuilt();
1547             mInformationalRankingExpressions.addAll(informationalRankingExpressions);
1548             return this;
1549         }
1550 
1551         /** Clears all informational ranking expressions. */
1552         @ExperimentalAppSearchApi
1553         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1554         @CanIgnoreReturnValue
clearInformationalRankingExpressions()1555         public @NonNull Builder clearInformationalRankingExpressions() {
1556             resetIfBuilt();
1557             mInformationalRankingExpressions.clear();
1558             return this;
1559         }
1560 
1561         /**
1562          * Sets an optional log tag to indicate the source of this search.
1563          *
1564          * <p>Some AppSearch implementations may log a hash of this tag using statsd. This tag
1565          * may be used for tracing performance issues and crashes to a component of an app.
1566          *
1567          * <p>Call this method and give a unique value if you want to distinguish this search
1568          * scenario with other search scenarios during performance analysis.
1569          *
1570          * <p>Under no circumstances will AppSearch log the raw String value using statsd, but it
1571          * will be provided as-is to custom {@code AppSearchLogger} implementations you have
1572          * registered in your app.
1573          *
1574          * @param searchSourceLogTag A String to indicate the source caller of this search. It is
1575          *                           used to label the search statsd for performance analysis. It
1576          *                           is not the tag we are using in {@link android.util.Log}. The
1577          *                           length of the teg should between 1 and 100.
1578          */
1579         @CanIgnoreReturnValue
1580         @RequiresFeature(
1581                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1582                 name = Features.SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG)
1583         @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SET_SEARCH_SOURCE_LOG_TAG)
setSearchSourceLogTag(@onNull String searchSourceLogTag)1584         public @NonNull Builder setSearchSourceLogTag(@NonNull String searchSourceLogTag) {
1585             Preconditions.checkStringNotEmpty(searchSourceLogTag);
1586             Preconditions.checkArgument(searchSourceLogTag.length() <= 100,
1587                     "The maximum supported tag length is 100. This tag is too long: "
1588                             + searchSourceLogTag.length());
1589             resetIfBuilt();
1590             mSearchSourceLogTag = searchSourceLogTag;
1591             return this;
1592         }
1593 
1594         /** Clears the log tag that indicates the source of this search. */
1595         @ExperimentalAppSearchApi
1596         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1597         @CanIgnoreReturnValue
clearSearchSourceLogTag()1598         public @NonNull Builder clearSearchSourceLogTag() {
1599             resetIfBuilt();
1600             mSearchSourceLogTag = null;
1601             return this;
1602         }
1603 
1604         /**
1605          * Sets the order of returned search results, the default is
1606          * {@link #ORDER_DESCENDING}, meaning that results with higher scores come first.
1607          *
1608          * <p>This order field will be ignored if RankingStrategy = {@code RANKING_STRATEGY_NONE}.
1609          */
1610         @CanIgnoreReturnValue
setOrder(@rder int order)1611         public @NonNull Builder setOrder(@Order int order) {
1612             Preconditions.checkArgumentInRange(order, ORDER_DESCENDING, ORDER_ASCENDING,
1613                     "Result ranking order");
1614             resetIfBuilt();
1615             mOrder = order;
1616             return this;
1617         }
1618 
1619         /**
1620          * Sets the {@code snippetCount} such that the first {@code snippetCount} documents based
1621          * on the ranking strategy will have snippet information provided.
1622          *
1623          * <p>The list returned from {@link SearchResult#getMatchInfos} will contain at most this
1624          * many entries.
1625          *
1626          * <p>If set to 0 (default), snippeting is disabled and the list returned from
1627          * {@link SearchResult#getMatchInfos} will be empty.
1628          */
1629         @CanIgnoreReturnValue
setSnippetCount( @ntRangefrom = 0, to = MAX_SNIPPET_COUNT) int snippetCount)1630         public @NonNull SearchSpec.Builder setSnippetCount(
1631                 @IntRange(from = 0, to = MAX_SNIPPET_COUNT) int snippetCount) {
1632             Preconditions.checkArgumentInRange(snippetCount, 0, MAX_SNIPPET_COUNT, "snippetCount");
1633             resetIfBuilt();
1634             mSnippetCount = snippetCount;
1635             return this;
1636         }
1637 
1638         /**
1639          * Sets {@code snippetCountPerProperty}. Only the first {@code snippetCountPerProperty}
1640          * snippets for each property of each {@link GenericDocument} will contain snippet
1641          * information.
1642          *
1643          * <p>If set to 0, snippeting is disabled and the list
1644          * returned from {@link SearchResult#getMatchInfos} will be empty.
1645          *
1646          * <p>The default behavior is to snippet all matches a property contains, up to the maximum
1647          * value of 10,000.
1648          */
1649         @CanIgnoreReturnValue
setSnippetCountPerProperty( @ntRangefrom = 0, to = MAX_SNIPPET_PER_PROPERTY_COUNT) int snippetCountPerProperty)1650         public @NonNull SearchSpec.Builder setSnippetCountPerProperty(
1651                 @IntRange(from = 0, to = MAX_SNIPPET_PER_PROPERTY_COUNT)
1652                 int snippetCountPerProperty) {
1653             Preconditions.checkArgumentInRange(snippetCountPerProperty,
1654                     0, MAX_SNIPPET_PER_PROPERTY_COUNT, "snippetCountPerProperty");
1655             resetIfBuilt();
1656             mSnippetCountPerProperty = snippetCountPerProperty;
1657             return this;
1658         }
1659 
1660         /**
1661          * Sets {@code maxSnippetSize}, the maximum snippet size. Snippet windows start at
1662          * {@code maxSnippetSize/2} bytes before the middle of the matching token and end at
1663          * {@code maxSnippetSize/2} bytes after the middle of the matching token. It respects
1664          * token boundaries, therefore the returned window may be smaller than requested.
1665          *
1666          * <p> Setting {@code maxSnippetSize} to 0 will disable windowing and an empty String will
1667          * be returned. If matches enabled is also set to false, then snippeting is disabled.
1668          *
1669          * <p>For example, {@code maxSnippetSize} = 16. "foo bar baz bat rat" with a query of "baz"
1670          * will return a window of "bar baz bat" which is only 11 bytes long.
1671          */
1672         @CanIgnoreReturnValue
setMaxSnippetSize( @ntRangefrom = 0, to = MAX_SNIPPET_SIZE_LIMIT) int maxSnippetSize)1673         public @NonNull SearchSpec.Builder setMaxSnippetSize(
1674                 @IntRange(from = 0, to = MAX_SNIPPET_SIZE_LIMIT) int maxSnippetSize) {
1675             Preconditions.checkArgumentInRange(
1676                     maxSnippetSize, 0, MAX_SNIPPET_SIZE_LIMIT, "maxSnippetSize");
1677             resetIfBuilt();
1678             mMaxSnippetSize = maxSnippetSize;
1679             return this;
1680         }
1681 
1682         /**
1683          * Sets whether to retrieve embedding match infos as a part of
1684          * {@link SearchResult#getMatchInfos()}.
1685          *
1686          * <p>Note that this does not modify the snippet count fields, and any retrieved
1687          * embedding match infos also count toward the limit set in
1688          * {@link SearchSpec#getSnippetCount()} and
1689          * {@link SearchSpec#getSnippetCountPerProperty()}.
1690          */
1691         @CanIgnoreReturnValue
1692         @RequiresFeature(
1693                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1694                 name = Features.SEARCH_EMBEDDING_MATCH_INFO)
1695         @FlaggedApi(Flags.FLAG_ENABLE_EMBEDDING_MATCH_INFO)
1696         @ExperimentalAppSearchApi
1697         @SuppressLint("MissingGetterMatchingBuilder")
setRetrieveEmbeddingMatchInfos( boolean retrieveEmbeddingMatchInfos)1698         public @NonNull Builder setRetrieveEmbeddingMatchInfos(
1699                 boolean retrieveEmbeddingMatchInfos) {
1700             resetIfBuilt();
1701             mRetrieveEmbeddingMatchInfos = retrieveEmbeddingMatchInfos;
1702             return this;
1703         }
1704 
1705         /**
1706          * Adds property paths for the specified type to be used for projection. If property
1707          * paths are added for a type, then only the properties referred to will be retrieved for
1708          * results of that type. If a property path that is specified isn't present in a result,
1709          * it will be ignored for that result. Property paths cannot be null.
1710          *
1711          * @see #addProjectionPaths
1712          *
1713          * @param schema a string corresponding to the schema to add projections to.
1714          * @param propertyPaths the projections to add.
1715          */
1716         @CanIgnoreReturnValue
addProjection( @onNull String schema, @NonNull Collection<String> propertyPaths)1717         public @NonNull SearchSpec.Builder addProjection(
1718                 @NonNull String schema, @NonNull Collection<String> propertyPaths) {
1719             Preconditions.checkNotNull(schema);
1720             Preconditions.checkNotNull(propertyPaths);
1721             resetIfBuilt();
1722             ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
1723             for (String propertyPath : propertyPaths) {
1724                 Preconditions.checkNotNull(propertyPath);
1725                 propertyPathsArrayList.add(propertyPath);
1726             }
1727             mProjectionTypePropertyMasks.putStringArrayList(schema, propertyPathsArrayList);
1728             return this;
1729         }
1730 
1731         /**
1732          * Adds property paths for the specified type to be used for projection. If property
1733          * paths are added for a type, then only the properties referred to will be retrieved for
1734          * results of that type. If a property path that is specified isn't present in a result,
1735          * it will be ignored for that result. Property paths cannot be null.
1736          *
1737          * <p>If no property paths are added for a particular type, then all properties of
1738          * results of that type will be retrieved.
1739          *
1740          * <p>If property path is added for the
1741          * {@link SearchSpec#SCHEMA_TYPE_WILDCARD}, then those property paths will apply to all
1742          * results, excepting any types that have their own, specific property paths set.
1743          *
1744          * <p>Suppose the following document is in the index.
1745          * <pre>{@code
1746          * Email: Document {
1747          *   sender: Document {
1748          *     name: "Mr. Person"
1749          *     email: "mrperson123@google.com"
1750          *   }
1751          *   recipients: [
1752          *     Document {
1753          *       name: "John Doe"
1754          *       email: "johndoe123@google.com"
1755          *     }
1756          *     Document {
1757          *       name: "Jane Doe"
1758          *       email: "janedoe123@google.com"
1759          *     }
1760          *   ]
1761          *   subject: "IMPORTANT"
1762          *   body: "Limited time offer!"
1763          * }
1764          * }</pre>
1765          *
1766          * <p>Then, suppose that a query for "important" is issued with the following projection
1767          * type property paths:
1768          * <pre>{@code
1769          * {schema: "Email", ["subject", "sender.name", "recipients.name"]}
1770          * }</pre>
1771          *
1772          * <p>The above document will be returned as:
1773          * <pre>{@code
1774          * Email: Document {
1775          *   sender: Document {
1776          *     name: "Mr. Body"
1777          *   }
1778          *   recipients: [
1779          *     Document {
1780          *       name: "John Doe"
1781          *     }
1782          *     Document {
1783          *       name: "Jane Doe"
1784          *     }
1785          *   ]
1786          *   subject: "IMPORTANT"
1787          * }
1788          * }</pre>
1789          *
1790          * @param schema a string corresponding to the schema to add projections to.
1791          * @param propertyPaths the projections to add.
1792          */
1793         @CanIgnoreReturnValue
addProjectionPaths( @onNull String schema, @NonNull Collection<PropertyPath> propertyPaths)1794         public @NonNull SearchSpec.Builder addProjectionPaths(
1795                 @NonNull String schema, @NonNull Collection<PropertyPath> propertyPaths) {
1796             Preconditions.checkNotNull(schema);
1797             Preconditions.checkNotNull(propertyPaths);
1798             ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
1799             for (PropertyPath propertyPath : propertyPaths) {
1800                 propertyPathsArrayList.add(propertyPath.toString());
1801             }
1802             return addProjection(schema, propertyPathsArrayList);
1803         }
1804 
1805 // @exportToFramework:startStrip()
1806         /**
1807          * Adds property paths for the Document class to be used for projection. If property
1808          * paths are added for a document class, then only the properties referred to will be
1809          * retrieved for results of that type. If a property path that is specified isn't present
1810          * in a result, it will be ignored for that result. Property paths cannot be null.
1811          *
1812          * @see #addProjection
1813          *
1814          * @param documentClass a class, annotated with @Document, corresponding to the schema to
1815          *                      add projections to.
1816          * @param propertyPaths the projections to add.
1817          */
1818         @CanIgnoreReturnValue
1819         @SuppressLint("MissingGetterMatchingBuilder")  // Projections available from getProjections
addProjectionsForDocumentClass( @onNull java.lang.Class<?> documentClass, @NonNull Collection<String> propertyPaths)1820         public @NonNull SearchSpec.Builder addProjectionsForDocumentClass(
1821                 @NonNull java.lang.Class<?> documentClass,
1822                 @NonNull Collection<String> propertyPaths)
1823                 throws AppSearchException {
1824             Preconditions.checkNotNull(documentClass);
1825             resetIfBuilt();
1826             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
1827             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
1828             return addProjection(factory.getSchemaName(), propertyPaths);
1829         }
1830 
1831         /**
1832          * Adds property paths for the specified Document class to be used for projection.
1833          * @see #addProjectionPaths
1834          *
1835          * @param documentClass a class, annotated with @Document, corresponding to the schema to
1836          *                      add projections to.
1837          * @param propertyPaths the projections to add.
1838          */
1839         @CanIgnoreReturnValue
1840         @SuppressLint("MissingGetterMatchingBuilder")  // Projections available from getProjections
addProjectionPathsForDocumentClass( @onNull java.lang.Class<?> documentClass, @NonNull Collection<PropertyPath> propertyPaths)1841         public @NonNull SearchSpec.Builder addProjectionPathsForDocumentClass(
1842                 @NonNull java.lang.Class<?> documentClass,
1843                 @NonNull Collection<PropertyPath> propertyPaths)
1844                 throws AppSearchException {
1845             Preconditions.checkNotNull(documentClass);
1846             resetIfBuilt();
1847             ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
1848             for (PropertyPath propertyPath : propertyPaths) {
1849                 propertyPathsArrayList.add(propertyPath.toString());
1850             }
1851             return addProjectionsForDocumentClass(documentClass, propertyPathsArrayList);
1852         }
1853 // @exportToFramework:endStrip()
1854 
1855         /** Clears the projections for all schema types. */
1856         @ExperimentalAppSearchApi
1857         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1858         @CanIgnoreReturnValue
clearProjections()1859         public @NonNull Builder clearProjections() {
1860             resetIfBuilt();
1861             mProjectionTypePropertyMasks.clear();
1862             return this;
1863         }
1864 
1865         /**
1866          * Sets the maximum number of results to return for each group, where groups are defined
1867          * by grouping type.
1868          *
1869          * <p>Calling this method will override any previous calls. So calling {@code
1870          * setResultGrouping(GROUPING_TYPE_PER_PACKAGE, 7)} and then calling {@code
1871          * setResultGrouping(GROUPING_TYPE_PER_PACKAGE, 2)} will result in only the latter, a limit
1872          * of two results per package, being applied. Or calling {@code setResultGrouping
1873          * (GROUPING_TYPE_PER_PACKAGE, 1)} and then calling {@code setResultGrouping
1874          * (GROUPING_TYPE_PER_PACKAGE | GROUPING_PER_NAMESPACE, 5)} will result in five results per
1875          * package per namespace.
1876          *
1877          * @param groupingTypeFlags One or more combination of grouping types.
1878          * @param limit             Number of results to return per {@code groupingTypeFlags}.
1879          * @throws IllegalArgumentException if groupingTypeFlags is zero.
1880          */
1881         // Individual parameters available from getResultGroupingTypeFlags and
1882         // getResultGroupingLimit
1883         @CanIgnoreReturnValue
1884         @SuppressLint("MissingGetterMatchingBuilder")
setResultGrouping(@roupingType int groupingTypeFlags, int limit)1885         public @NonNull Builder setResultGrouping(@GroupingType int groupingTypeFlags, int limit) {
1886             Preconditions.checkState(
1887                     groupingTypeFlags != 0, "Result grouping type cannot be zero.");
1888             resetIfBuilt();
1889             mGroupingTypeFlags = groupingTypeFlags;
1890             mGroupingLimit = limit;
1891             return this;
1892         }
1893 
1894         /** Clears the result grouping and limit. */
1895         @ExperimentalAppSearchApi
1896         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1897         @CanIgnoreReturnValue
clearResultGrouping()1898         public @NonNull Builder clearResultGrouping() {
1899             resetIfBuilt();
1900             mGroupingTypeFlags = 0;
1901             mGroupingLimit = 0;
1902             return this;
1903         }
1904 
1905         /**
1906          * Sets property weights by schema type and property path.
1907          *
1908          * <p>Property weights are used to promote and demote query term matches within a
1909          * {@link GenericDocument} property when applying scoring.
1910          *
1911          * <p>Property weights must be positive values (greater than 0). A property's weight is
1912          * multiplied with that property's scoring contribution. This means weights set between 0.0
1913          * and 1.0 demote scoring contributions by a term match within the property. Weights set
1914          * above 1.0 promote scoring contributions by a term match within the property.
1915          *
1916          * <p>Properties that exist in the {@link AppSearchSchema}, but do not have a weight
1917          * explicitly set will be given a default weight of 1.0.
1918          *
1919          * <p>Weights set for property paths that do not exist in the {@link AppSearchSchema} will
1920          * be discarded and not affect scoring.
1921          *
1922          * <p><b>NOTE:</b> Property weights only affect scoring for query-dependent scoring
1923          * strategies, such as {@link #RANKING_STRATEGY_RELEVANCE_SCORE}.
1924          *
1925          * <!--@exportToFramework:ifJetpack()-->
1926          * <p>This information may not be available depending on the backend and Android API
1927          * level. To ensure it is available, call {@link Features#isFeatureSupported}.
1928          * <!--@exportToFramework:else()-->
1929          *
1930          * @param schemaType          the schema type to set property weights for.
1931          * @param propertyPathWeights a {@link Map} of property paths of the schema type to the
1932          *                            weight to set for that property.
1933          * @throws IllegalArgumentException if a weight is equal to or less than 0.0.
1934          */
1935         @CanIgnoreReturnValue
1936         @RequiresFeature(
1937                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1938                 name = Features.SEARCH_SPEC_PROPERTY_WEIGHTS)
setPropertyWeights(@onNull String schemaType, @NonNull Map<String, Double> propertyPathWeights)1939         public @NonNull SearchSpec.Builder setPropertyWeights(@NonNull String schemaType,
1940                 @NonNull Map<String, Double> propertyPathWeights) {
1941             Preconditions.checkNotNull(schemaType);
1942             Preconditions.checkNotNull(propertyPathWeights);
1943 
1944             Bundle propertyPathBundle = new Bundle();
1945             for (Map.Entry<String, Double> propertyPathWeightEntry :
1946                     propertyPathWeights.entrySet()) {
1947                 String propertyPath = Preconditions.checkNotNull(propertyPathWeightEntry.getKey());
1948                 Double weight = Preconditions.checkNotNull(propertyPathWeightEntry.getValue());
1949                 if (weight <= 0.0) {
1950                     throw new IllegalArgumentException("Cannot set non-positive property weight "
1951                             + "value " + weight + " for property path: " + propertyPath);
1952                 }
1953                 propertyPathBundle.putDouble(propertyPath, weight);
1954             }
1955             mTypePropertyWeights.putBundle(schemaType, propertyPathBundle);
1956             return this;
1957         }
1958 
1959         /** Clears the property weights for all schema types. */
1960         @ExperimentalAppSearchApi
1961         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1962         @CanIgnoreReturnValue
clearPropertyWeights()1963         public @NonNull Builder clearPropertyWeights() {
1964             resetIfBuilt();
1965             mTypePropertyWeights.clear();
1966             return this;
1967         }
1968 
1969         /**
1970          * Specifies which documents to join with, and how to join.
1971          *
1972          * <p> If the ranking strategy is {@link #RANKING_STRATEGY_JOIN_AGGREGATE_SCORE}, and the
1973          * JoinSpec is null, {@link #build} will throw an {@link AppSearchException}.
1974          *
1975          * @param joinSpec a specification on how to perform the Join operation.
1976          */
1977         @CanIgnoreReturnValue
1978         @RequiresFeature(
1979                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1980                 name = Features.JOIN_SPEC_AND_QUALIFIED_ID)
setJoinSpec(@onNull JoinSpec joinSpec)1981         public @NonNull Builder setJoinSpec(@NonNull JoinSpec joinSpec) {
1982             resetIfBuilt();
1983             mJoinSpec = Preconditions.checkNotNull(joinSpec);
1984             return this;
1985         }
1986 
1987         /** Clears the {@link JoinSpec}. */
1988         @ExperimentalAppSearchApi
1989         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
1990         @CanIgnoreReturnValue
clearJoinSpec()1991         public @NonNull Builder clearJoinSpec() {
1992             resetIfBuilt();
1993             mJoinSpec = null;
1994             return this;
1995         }
1996 
1997         /**
1998          * Sets property weights by schema type and property path.
1999          *
2000          * <p>Property weights are used to promote and demote query term matches within a
2001          * {@link GenericDocument} property when applying scoring.
2002          *
2003          * <p>Property weights must be positive values (greater than 0). A property's weight is
2004          * multiplied with that property's scoring contribution. This means weights set between 0.0
2005          * and 1.0 demote scoring contributions by a term match within the property. Weights set
2006          * above 1.0 promote scoring contributions by a term match within the property.
2007          *
2008          * <p>Properties that exist in the {@link AppSearchSchema}, but do not have a weight
2009          * explicitly set will be given a default weight of 1.0.
2010          *
2011          * <p>Weights set for property paths that do not exist in the {@link AppSearchSchema} will
2012          * be discarded and not affect scoring.
2013          *
2014          * <p><b>NOTE:</b> Property weights only affect scoring for query-dependent scoring
2015          * strategies, such as {@link #RANKING_STRATEGY_RELEVANCE_SCORE}.
2016          *
2017          * <!--@exportToFramework:ifJetpack()-->
2018          * <p>This information may not be available depending on the backend and Android API
2019          * level. To ensure it is available, call {@link Features#isFeatureSupported}.
2020          * <!--@exportToFramework:else()-->
2021          *
2022          * @param schemaType          the schema type to set property weights for.
2023          * @param propertyPathWeights a {@link Map} of property paths of the schema type to the
2024          *                            weight to set for that property.
2025          * @throws IllegalArgumentException if a weight is equal to or less than 0.0.
2026          */
2027         @CanIgnoreReturnValue
2028         @RequiresFeature(
2029                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2030                 name = Features.SEARCH_SPEC_PROPERTY_WEIGHTS)
setPropertyWeightPaths(@onNull String schemaType, @NonNull Map<PropertyPath, Double> propertyPathWeights)2031         public @NonNull SearchSpec.Builder setPropertyWeightPaths(@NonNull String schemaType,
2032                 @NonNull Map<PropertyPath, Double> propertyPathWeights) {
2033             Preconditions.checkNotNull(propertyPathWeights);
2034 
2035             Map<String, Double> propertyWeights = new ArrayMap<>(propertyPathWeights.size());
2036             for (Map.Entry<PropertyPath, Double> propertyPathWeightEntry :
2037                     propertyPathWeights.entrySet()) {
2038                 PropertyPath propertyPath =
2039                         Preconditions.checkNotNull(propertyPathWeightEntry.getKey());
2040                 propertyWeights.put(propertyPath.toString(), propertyPathWeightEntry.getValue());
2041             }
2042             return setPropertyWeights(schemaType, propertyWeights);
2043         }
2044 
2045 // @exportToFramework:startStrip()
2046 
2047         /**
2048          * Sets property weights by schema type and property path.
2049          *
2050          * <p>Property weights are used to promote and demote query term matches within a
2051          * {@link GenericDocument} property when applying scoring.
2052          *
2053          * <p>Property weights must be positive values (greater than 0). A property's weight is
2054          * multiplied with that property's scoring contribution. This means weights set between 0.0
2055          * and 1.0 demote scoring contributions by a term match within the property. Weights set
2056          * above 1.0 promote scoring contributions by a term match within the property.
2057          *
2058          * <p>Properties that exist in the {@link AppSearchSchema}, but do not have a weight
2059          * explicitly set will be given a default weight of 1.0.
2060          *
2061          * <p>Weights set for property paths that do not exist in the {@link AppSearchSchema} will
2062          * be discarded and not affect scoring.
2063          *
2064          * <p><b>NOTE:</b> Property weights only affect scoring for query-dependent scoring
2065          * strategies, such as {@link #RANKING_STRATEGY_RELEVANCE_SCORE}.
2066          *
2067          * <!--@exportToFramework:ifJetpack()-->
2068          * <p>This information may not be available depending on the backend and Android API
2069          * level. To ensure it is available, call {@link Features#isFeatureSupported}.
2070          * <!--@exportToFramework:else()-->
2071          *
2072          * @param documentClass a class, annotated with @Document, corresponding to the schema to
2073          *                      set property weights for.
2074          * @param propertyPathWeights a {@link Map} of property paths of the schema type to the
2075          *                            weight to set for that property.
2076          * @throws AppSearchException if no factory for this document class could be found on the
2077          *                            classpath
2078          * @throws IllegalArgumentException if a weight is equal to or less than 0.0.
2079          */
2080         @CanIgnoreReturnValue
2081         @SuppressLint("MissingGetterMatchingBuilder")
2082         @RequiresFeature(
2083                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2084                 name = Features.SEARCH_SPEC_PROPERTY_WEIGHTS)
setPropertyWeightsForDocumentClass( @onNull java.lang.Class<?> documentClass, @NonNull Map<String, Double> propertyPathWeights)2085         public @NonNull SearchSpec.Builder setPropertyWeightsForDocumentClass(
2086                 @NonNull java.lang.Class<?> documentClass,
2087                 @NonNull Map<String, Double> propertyPathWeights) throws AppSearchException {
2088             Preconditions.checkNotNull(documentClass);
2089             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
2090             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
2091             return setPropertyWeights(factory.getSchemaName(), propertyPathWeights);
2092         }
2093 
2094         /**
2095          * Sets property weights by schema type and property path.
2096          *
2097          * <p>Property weights are used to promote and demote query term matches within a
2098          * {@link GenericDocument} property when applying scoring.
2099          *
2100          * <p>Property weights must be positive values (greater than 0). A property's weight is
2101          * multiplied with that property's scoring contribution. This means weights set between 0.0
2102          * and 1.0 demote scoring contributions by a term match within the property. Weights set
2103          * above 1.0 promote scoring contributions by a term match within the property.
2104          *
2105          * <p>Properties that exist in the {@link AppSearchSchema}, but do not have a weight
2106          * explicitly set will be given a default weight of 1.0.
2107          *
2108          * <p>Weights set for property paths that do not exist in the {@link AppSearchSchema} will
2109          * be discarded and not affect scoring.
2110          *
2111          * <p><b>NOTE:</b> Property weights only affect scoring for query-dependent scoring
2112          * strategies, such as {@link #RANKING_STRATEGY_RELEVANCE_SCORE}.
2113          *
2114          * <!--@exportToFramework:ifJetpack()-->
2115          * <p>This information may not be available depending on the backend and Android API
2116          * level. To ensure it is available, call {@link Features#isFeatureSupported}.
2117          * <!--@exportToFramework:else()-->
2118          *
2119          * @param documentClass a class, annotated with @Document, corresponding to the schema to
2120          *                      set property weights for.
2121          * @param propertyPathWeights a {@link Map} of property paths of the schema type to the
2122          *                            weight to set for that property.
2123          * @throws AppSearchException if no factory for this document class could be found on the
2124          *                            classpath
2125          * @throws IllegalArgumentException if a weight is equal to or less than 0.0.
2126          */
2127         @CanIgnoreReturnValue
2128         @SuppressLint("MissingGetterMatchingBuilder")
2129         @RequiresFeature(
2130                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2131                 name = Features.SEARCH_SPEC_PROPERTY_WEIGHTS)
setPropertyWeightPathsForDocumentClass( @onNull java.lang.Class<?> documentClass, @NonNull Map<PropertyPath, Double> propertyPathWeights)2132         public @NonNull SearchSpec.Builder setPropertyWeightPathsForDocumentClass(
2133                 @NonNull java.lang.Class<?> documentClass,
2134                 @NonNull Map<PropertyPath, Double> propertyPathWeights) throws AppSearchException {
2135             Preconditions.checkNotNull(documentClass);
2136             DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
2137             DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
2138             return setPropertyWeightPaths(factory.getSchemaName(), propertyPathWeights);
2139         }
2140 // @exportToFramework:endStrip()
2141         /**
2142          * Adds an embedding search to {@link SearchSpec} Entry, which will be referred in the
2143          * query expression and the ranking expression for embedding search.
2144          *
2145          * @see AppSearchSession#search
2146          * @see SearchSpec.Builder#setRankingStrategy(String)
2147          */
2148         @CanIgnoreReturnValue
2149         @RequiresFeature(
2150                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2151                 name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
2152         @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
addEmbeddingParameters( @onNull EmbeddingVector... searchEmbeddings)2153         public @NonNull Builder addEmbeddingParameters(
2154                 @NonNull EmbeddingVector... searchEmbeddings) {
2155             Preconditions.checkNotNull(searchEmbeddings);
2156             resetIfBuilt();
2157             return addEmbeddingParameters(Arrays.asList(searchEmbeddings));
2158         }
2159 
2160         /**
2161          * Adds an embedding search to {@link SearchSpec} Entry, which will be referred in the
2162          * query expression and the ranking expression for embedding search.
2163          *
2164          * @see AppSearchSession#search
2165          * @see SearchSpec.Builder#setRankingStrategy(String)
2166          */
2167         @CanIgnoreReturnValue
2168         @RequiresFeature(
2169                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2170                 name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
2171         @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
addEmbeddingParameters( @onNull Collection<EmbeddingVector> searchEmbeddings)2172         public @NonNull Builder addEmbeddingParameters(
2173                 @NonNull Collection<EmbeddingVector> searchEmbeddings) {
2174             Preconditions.checkNotNull(searchEmbeddings);
2175             resetIfBuilt();
2176             mEmbeddingParameters.addAll(searchEmbeddings);
2177             return this;
2178         }
2179 
2180         /** Clears the embedding parameters. */
2181         @ExperimentalAppSearchApi
2182         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
2183         @CanIgnoreReturnValue
clearEmbeddingParameters()2184         public @NonNull Builder clearEmbeddingParameters() {
2185             resetIfBuilt();
2186             mEmbeddingParameters.clear();
2187             return this;
2188         }
2189 
2190         /**
2191          * Sets the default embedding metric type used for embedding search
2192          * (see {@link AppSearchSession#search}) and ranking
2193          * (see {@link SearchSpec.Builder#setRankingStrategy(String)}).
2194          *
2195          * <p>If this method is not called, the default embedding search metric type is
2196          * {@link SearchSpec#EMBEDDING_SEARCH_METRIC_TYPE_COSINE}. Metrics specified within
2197          * "semanticSearch" or "matchedSemanticScores" functions in search/ranking expressions
2198          * will override this default.
2199          */
2200         @CanIgnoreReturnValue
2201         @RequiresFeature(
2202                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2203                 name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
2204         @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
setDefaultEmbeddingSearchMetricType( @mbeddingSearchMetricType int defaultEmbeddingSearchMetricType)2205         public @NonNull Builder setDefaultEmbeddingSearchMetricType(
2206                 @EmbeddingSearchMetricType int defaultEmbeddingSearchMetricType) {
2207             Preconditions.checkArgumentInRange(defaultEmbeddingSearchMetricType,
2208                     EMBEDDING_SEARCH_METRIC_TYPE_COSINE,
2209                     EMBEDDING_SEARCH_METRIC_TYPE_EUCLIDEAN, "Embedding search metric type");
2210             resetIfBuilt();
2211             mDefaultEmbeddingSearchMetricType = defaultEmbeddingSearchMetricType;
2212             return this;
2213         }
2214 
2215         /**
2216          * Adds Strings to the list of String parameters that can be referenced in the query through
2217          * the "getSearchStringParameter({index})" function.
2218          *
2219          * @see AppSearchSession#search
2220          */
2221         @CanIgnoreReturnValue
2222         @RequiresFeature(
2223                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2224                 name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
2225         @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
addSearchStringParameters( @onNull String... searchStringParameters)2226         public @NonNull Builder addSearchStringParameters(
2227                 @NonNull String... searchStringParameters) {
2228             Preconditions.checkNotNull(searchStringParameters);
2229             resetIfBuilt();
2230             return addSearchStringParameters(Arrays.asList(searchStringParameters));
2231         }
2232 
2233         /**
2234          * Adds Strings to the list of String parameters that can be referenced in the query through
2235          * the "getSearchStringParameter({index})" function.
2236          *
2237          * @see AppSearchSession#search
2238          */
2239         @CanIgnoreReturnValue
2240         @RequiresFeature(
2241                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2242                 name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
2243         @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS)
addSearchStringParameters( @onNull List<String> searchStringParameters)2244         public @NonNull Builder addSearchStringParameters(
2245                 @NonNull List<String> searchStringParameters) {
2246             Preconditions.checkNotNull(searchStringParameters);
2247             resetIfBuilt();
2248             mSearchStringParameters.addAll(searchStringParameters);
2249             return this;
2250         }
2251 
2252         /**
2253          * Clears the list of String parameters that can be referenced in the query through the
2254          * "getSearchStringParameter({index})" function.
2255          */
2256         @ExperimentalAppSearchApi
2257         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
2258         @CanIgnoreReturnValue
clearSearchStringParameters()2259         public @NonNull Builder clearSearchStringParameters() {
2260             resetIfBuilt();
2261             mSearchStringParameters.clear();
2262             return this;
2263         }
2264 
2265         /**
2266          * Sets the NUMERIC_SEARCH feature as enabled/disabled according to the enabled parameter.
2267          *
2268          * @param enabled Enables the feature if true, otherwise disables it.
2269          *
2270          * <p>If disabled, disallows use of
2271          * {@link AppSearchSchema.LongPropertyConfig#INDEXING_TYPE_RANGE} and all other numeric
2272          * querying features.
2273          */
2274         @CanIgnoreReturnValue
2275         @RequiresFeature(
2276                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2277                 name = Features.NUMERIC_SEARCH)
setNumericSearchEnabled(boolean enabled)2278         public @NonNull Builder setNumericSearchEnabled(boolean enabled) {
2279             modifyEnabledFeature(FeatureConstants.NUMERIC_SEARCH, enabled);
2280             return this;
2281         }
2282 
2283         /**
2284          * Sets the VERBATIM_SEARCH feature as enabled/disabled according to the enabled parameter.
2285          *
2286          * @param enabled Enables the feature if true, otherwise disables it
2287          *
2288          * <p>If disabled, disallows use of
2289          * {@link AppSearchSchema.StringPropertyConfig#TOKENIZER_TYPE_VERBATIM} and all other
2290          * verbatim search features within the query language that allows clients to search
2291          * using the verbatim string operator.
2292          *
2293          * <p>For example, The verbatim string operator '"foo/bar" OR baz' will ensure that
2294          * 'foo/bar' is treated as a single 'verbatim' token.
2295          */
2296         @CanIgnoreReturnValue
2297         @RequiresFeature(
2298                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2299                 name = Features.VERBATIM_SEARCH)
setVerbatimSearchEnabled(boolean enabled)2300         public @NonNull Builder setVerbatimSearchEnabled(boolean enabled) {
2301             modifyEnabledFeature(FeatureConstants.VERBATIM_SEARCH, enabled);
2302             return this;
2303         }
2304 
2305         /**
2306          * Sets the LIST_FILTER_QUERY_LANGUAGE feature as enabled/disabled according to the
2307          * enabled parameter.
2308          *
2309          * @param enabled Enables the feature if true, otherwise disables it.
2310          *
2311          * This feature covers the expansion of the query language to conform to the definition
2312          * of the list filters language (https://aip.dev/160). This includes:
2313          * <ul>
2314          * <li>addition of explicit 'AND' and 'NOT' operators</li>
2315          * <li>property restricts are allowed with grouping (ex. "prop:(a OR b)")</li>
2316          * <li>addition of custom functions to control matching</li>
2317          * </ul>
2318          *
2319          * <p>The newly added custom functions covered by this feature are:
2320          * <ul>
2321          * <li>createList(String...)</li>
2322          * <li>termSearch(String, {@code List<String>})</li>
2323          * </ul>
2324          *
2325          * <p>createList takes a variable number of strings and returns a list of strings.
2326          * It is for use with termSearch.
2327          *
2328          * <p>termSearch takes a query string that will be parsed according to the supported
2329          * query language and an optional list of strings that specify the properties to be
2330          * restricted to. This exists as a convenience for multiple property restricts. So,
2331          * for example, the query "(subject:foo OR body:foo) (subject:bar OR body:bar)"
2332          * could be rewritten as "termSearch(\"foo bar\", createList(\"subject\", \"bar\"))"
2333          */
2334         @CanIgnoreReturnValue
2335         @RequiresFeature(
2336                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2337                 name = Features.LIST_FILTER_QUERY_LANGUAGE)
setListFilterQueryLanguageEnabled(boolean enabled)2338         public @NonNull Builder setListFilterQueryLanguageEnabled(boolean enabled) {
2339             modifyEnabledFeature(FeatureConstants.LIST_FILTER_QUERY_LANGUAGE, enabled);
2340             return this;
2341         }
2342 
2343         /**
2344          * Sets the LIST_FILTER_HAS_PROPERTY_FUNCTION feature as enabled/disabled according to
2345          * the enabled parameter.
2346          *
2347          * @param enabled Enables the feature if true, otherwise disables it
2348          *
2349          * <p>If disabled, disallows the use of the "hasProperty" function. See
2350          * {@link AppSearchSession#search} for more details about the function.
2351          */
2352         @CanIgnoreReturnValue
2353         @RequiresFeature(
2354                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2355                 name = Features.LIST_FILTER_HAS_PROPERTY_FUNCTION)
2356         @FlaggedApi(Flags.FLAG_ENABLE_LIST_FILTER_HAS_PROPERTY_FUNCTION)
setListFilterHasPropertyFunctionEnabled(boolean enabled)2357         public @NonNull Builder setListFilterHasPropertyFunctionEnabled(boolean enabled) {
2358             modifyEnabledFeature(FeatureConstants.LIST_FILTER_HAS_PROPERTY_FUNCTION, enabled);
2359             return this;
2360         }
2361 
2362         /**
2363          * Sets the LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION feature as enabled/disabled
2364          * according to the enabled parameter.
2365          *
2366          * <p>If not enabled, the use of the "matchScoreExpression" function is disallowed. See
2367          * {@link AppSearchSession#search} for more details about the function.
2368          *
2369          * @param enabled Enables the feature if true, otherwise disables it
2370          */
2371         @CanIgnoreReturnValue
2372         @RequiresFeature(
2373                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2374                 name = Features.LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION)
2375         @ExperimentalAppSearchApi
2376         @FlaggedApi(Flags.FLAG_ENABLE_LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION)
setListFilterMatchScoreExpressionFunctionEnabled(boolean enabled)2377         public @NonNull Builder setListFilterMatchScoreExpressionFunctionEnabled(boolean enabled) {
2378             modifyEnabledFeature(
2379                     FeatureConstants.LIST_FILTER_MATCH_SCORE_EXPRESSION_FUNCTION, enabled);
2380             return this;
2381         }
2382 
2383         /**
2384          * Sets the ScorablePropertyRanking feature as enabled or disabled.
2385          *
2386          * <p>If enabled, 'getScorableProperty' function can be used in the advanced ranking
2387          * expression. For details, see {@link SearchSpec.Builder#setRankingStrategy(String)}.
2388          *
2389          * @param enabled Enables the feature if true, otherwise disables it.
2390          */
2391         @CanIgnoreReturnValue
2392         @RequiresFeature(
2393                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2394                 name = Features.SCHEMA_SCORABLE_PROPERTY_CONFIG)
2395         @ExperimentalAppSearchApi
2396         @FlaggedApi(Flags.FLAG_ENABLE_SCORABLE_PROPERTY)
setScorablePropertyRankingEnabled(boolean enabled)2397         public @NonNull Builder setScorablePropertyRankingEnabled(boolean enabled) {
2398             modifyEnabledFeature(FeatureConstants.SCHEMA_SCORABLE_PROPERTY_CONFIG, enabled);
2399             return this;
2400         }
2401 
2402         /**
2403          * Constructs a new {@link SearchSpec} from the contents of this builder.
2404          *
2405          * @throws IllegalArgumentException if property weights are provided with a
2406          *                                  ranking strategy that isn't
2407          *                                  RANKING_STRATEGY_RELEVANCE_SCORE.
2408          * @throws IllegalStateException if the ranking strategy is
2409          * {@link #RANKING_STRATEGY_JOIN_AGGREGATE_SCORE} and {@link #setJoinSpec} has never been
2410          * called.
2411          * @throws IllegalStateException if the aggregation scoring strategy has been set in
2412          * {@link JoinSpec#getAggregationScoringStrategy()} but the ranking strategy is not
2413          * {@link #RANKING_STRATEGY_JOIN_AGGREGATE_SCORE}.
2414          *
2415          */
build()2416         public @NonNull SearchSpec build() {
2417             if (mJoinSpec != null) {
2418                 if (mRankingStrategy != RANKING_STRATEGY_JOIN_AGGREGATE_SCORE
2419                         && mJoinSpec.getAggregationScoringStrategy()
2420                         != JoinSpec.AGGREGATION_SCORING_OUTER_RESULT_RANKING_SIGNAL) {
2421                     throw new IllegalStateException("Aggregate scoring strategy has been set in "
2422                             + "the nested JoinSpec, but ranking strategy is not "
2423                             + "RANKING_STRATEGY_JOIN_AGGREGATE_SCORE");
2424                 }
2425             } else if (mRankingStrategy == RANKING_STRATEGY_JOIN_AGGREGATE_SCORE) {
2426                 throw new IllegalStateException("Attempting to rank based on joined documents, but "
2427                         + "no JoinSpec provided");
2428             }
2429             if (!mTypePropertyWeights.isEmpty()
2430                     && mRankingStrategy != RANKING_STRATEGY_RELEVANCE_SCORE
2431                     && mRankingStrategy != RANKING_STRATEGY_ADVANCED_RANKING_EXPRESSION) {
2432                 throw new IllegalArgumentException("Property weights are only compatible with the "
2433                         + "RANKING_STRATEGY_RELEVANCE_SCORE and "
2434                         + "RANKING_STRATEGY_ADVANCED_RANKING_EXPRESSION ranking strategies.");
2435             }
2436 
2437             mBuilt = true;
2438             return new SearchSpec(mTermMatchType, mSchemas, mNamespaces,
2439                     mTypePropertyFilters, mPackageNames, mResultCountPerPage,
2440                     mRankingStrategy, mOrder, mSnippetCount, mSnippetCountPerProperty,
2441                     mMaxSnippetSize, mProjectionTypePropertyMasks, mGroupingTypeFlags,
2442                     mGroupingLimit, mTypePropertyWeights, mJoinSpec, mAdvancedRankingExpression,
2443                     new ArrayList<>(mEnabledFeatures), mSearchSourceLogTag, mEmbeddingParameters,
2444                     mDefaultEmbeddingSearchMetricType, mInformationalRankingExpressions,
2445                     mSearchStringParameters, mFilterDocumentIds, mRetrieveEmbeddingMatchInfos);
2446         }
2447 
resetIfBuilt()2448         private void resetIfBuilt() {
2449             if (mBuilt) {
2450                 mSchemas = new ArrayList<>(mSchemas);
2451                 mTypePropertyFilters = BundleUtil.deepCopy(mTypePropertyFilters);
2452                 mNamespaces = new ArrayList<>(mNamespaces);
2453                 mPackageNames = new ArrayList<>(mPackageNames);
2454                 mProjectionTypePropertyMasks = BundleUtil.deepCopy(mProjectionTypePropertyMasks);
2455                 mTypePropertyWeights = BundleUtil.deepCopy(mTypePropertyWeights);
2456                 mEmbeddingParameters = new ArrayList<>(mEmbeddingParameters);
2457                 mInformationalRankingExpressions = new ArrayList<>(
2458                         mInformationalRankingExpressions);
2459                 mSearchStringParameters = new ArrayList<>(mSearchStringParameters);
2460                 mFilterDocumentIds = new ArrayList<>(mFilterDocumentIds);
2461                 mBuilt = false;
2462             }
2463         }
2464 
modifyEnabledFeature(@onNull String feature, boolean enabled)2465         private void modifyEnabledFeature(@NonNull String feature, boolean enabled) {
2466             resetIfBuilt();
2467             if (enabled) {
2468                 mEnabledFeatures.add(feature);
2469             } else {
2470                 mEnabledFeatures.remove(feature);
2471             }
2472         }
2473     }
2474 }
2475