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