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