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