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