1 /* 2 * Copyright 2022 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.RequiresFeature; 29 import androidx.annotation.RestrictTo; 30 import androidx.appsearch.annotation.CanIgnoreReturnValue; 31 import androidx.appsearch.annotation.Document; 32 import androidx.appsearch.exceptions.AppSearchException; 33 import androidx.appsearch.flags.FlaggedApi; 34 import androidx.appsearch.flags.Flags; 35 import androidx.appsearch.safeparcel.AbstractSafeParcelable; 36 import androidx.appsearch.safeparcel.SafeParcelable; 37 import androidx.appsearch.safeparcel.stub.StubCreators.SearchSuggestionSpecCreator; 38 import androidx.appsearch.util.BundleUtil; 39 import androidx.collection.ArrayMap; 40 import androidx.collection.ArraySet; 41 import androidx.core.util.Preconditions; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Collection; 48 import java.util.Collections; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Set; 52 53 /** 54 * This class represents the specification logic for AppSearch. It can be used to set the filter 55 * and settings of search a suggestions. 56 * 57 * @see AppSearchSession#searchSuggestionAsync 58 */ 59 @SafeParcelable.Class(creator = "SearchSuggestionSpecCreator") 60 // TODO(b/384721898): Switch to JSpecify annotations 61 @SuppressWarnings({"HiddenSuperclass", "JSpecifyNullness"}) 62 public final class SearchSuggestionSpec extends AbstractSafeParcelable { 63 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 64 @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) 65 public static final @NonNull Parcelable.Creator<SearchSuggestionSpec> CREATOR = 66 new SearchSuggestionSpecCreator(); 67 68 @Field(id = 1, getter = "getFilterNamespaces") 69 private final @NonNull List<String> mFilterNamespaces; 70 71 @Field(id = 2, getter = "getFilterSchemas") 72 private final @NonNull List<String> mFilterSchemas; 73 74 // Maps are not supported by SafeParcelable fields, using Bundle instead. Here the key is 75 // schema type and value is a list of target property paths in that schema to search over. 76 @Field(id = 3) 77 final @NonNull Bundle mFilterProperties; 78 79 // Maps are not supported by SafeParcelable fields, using Bundle instead. Here the key is 80 // namespace and value is a list of target document ids in that namespace to search over. 81 @Field(id = 4) 82 final @NonNull Bundle mFilterDocumentIds; 83 84 @Field(id = 5, getter = "getRankingStrategy") 85 private final int mRankingStrategy; 86 87 @Field(id = 6, getter = "getMaximumResultCount") 88 private final int mMaximumResultCount; 89 90 @Field(id = 7, getter = "getSearchStringParameters") 91 private final @NonNull List<String> mSearchStringParameters; 92 93 /** @exportToFramework:hide */ 94 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 95 @Constructor SearchSuggestionSpec( @aramid = 1) @onNull List<String> filterNamespaces, @Param(id = 2) @NonNull List<String> filterSchemas, @Param(id = 3) @NonNull Bundle filterProperties, @Param(id = 4) @NonNull Bundle filterDocumentIds, @Param(id = 5) @SuggestionRankingStrategy int rankingStrategy, @Param(id = 6) int maximumResultCount, @Param(id = 7) @Nullable List<String> searchStringParameters)96 public SearchSuggestionSpec( 97 @Param(id = 1) @NonNull List<String> filterNamespaces, 98 @Param(id = 2) @NonNull List<String> filterSchemas, 99 @Param(id = 3) @NonNull Bundle filterProperties, 100 @Param(id = 4) @NonNull Bundle filterDocumentIds, 101 @Param(id = 5) @SuggestionRankingStrategy int rankingStrategy, 102 @Param(id = 6) int maximumResultCount, 103 @Param(id = 7) @Nullable List<String> searchStringParameters) { 104 Preconditions.checkArgument(maximumResultCount >= 1, 105 "MaximumResultCount must be positive."); 106 mFilterNamespaces = Preconditions.checkNotNull(filterNamespaces); 107 mFilterSchemas = Preconditions.checkNotNull(filterSchemas); 108 mFilterProperties = Preconditions.checkNotNull(filterProperties); 109 mFilterDocumentIds = Preconditions.checkNotNull(filterDocumentIds); 110 mRankingStrategy = rankingStrategy; 111 mMaximumResultCount = maximumResultCount; 112 mSearchStringParameters = 113 (searchStringParameters != null) 114 ? Collections.unmodifiableList(searchStringParameters) 115 : Collections.emptyList(); 116 } 117 118 /** 119 * Ranking Strategy for {@link SearchSuggestionResult}. 120 * 121 * @exportToFramework:hide 122 */ 123 @IntDef(value = { 124 SUGGESTION_RANKING_STRATEGY_NONE, 125 SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT, 126 SUGGESTION_RANKING_STRATEGY_TERM_FREQUENCY, 127 }) 128 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 129 @Retention(RetentionPolicy.SOURCE) 130 public @interface SuggestionRankingStrategy { 131 } 132 133 /** 134 * Ranked by the document count that contains the term. 135 * 136 * <p>Suppose the following document is in the index. 137 * <pre>Doc1 contains: term1 term2 term2 term2</pre> 138 * <pre>Doc2 contains: term1</pre> 139 * 140 * <p>Then, suppose that a search suggestion for "t" is issued with the DOCUMENT_COUNT, the 141 * returned {@link SearchSuggestionResult}s will be: term1, term2. The term1 will have higher 142 * score and appear in the results first. 143 */ 144 public static final int SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT = 0; 145 /** 146 * Ranked by the term appear frequency. 147 * 148 * <p>Suppose the following document is in the index. 149 * <pre>Doc1 contains: term1 term2 term2 term2</pre> 150 * <pre>Doc2 contains: term1</pre> 151 * 152 * <p>Then, suppose that a search suggestion for "t" is issued with the TERM_FREQUENCY, 153 * the returned {@link SearchSuggestionResult}s will be: term2, term1. The term2 will have 154 * higher score and appear in the results first. 155 */ 156 public static final int SUGGESTION_RANKING_STRATEGY_TERM_FREQUENCY = 1; 157 158 /** No Ranking, results are returned in arbitrary order. */ 159 public static final int SUGGESTION_RANKING_STRATEGY_NONE = 2; 160 161 /** 162 * Returns the maximum number of wanted suggestion that will be returned in the result object. 163 */ getMaximumResultCount()164 public int getMaximumResultCount() { 165 return mMaximumResultCount; 166 } 167 168 /** 169 * Returns the list of namespaces to search over. 170 * 171 * <p>If empty, will search over all namespaces. 172 */ getFilterNamespaces()173 public @NonNull List<String> getFilterNamespaces() { 174 if (mFilterNamespaces == null) { 175 return Collections.emptyList(); 176 } 177 return Collections.unmodifiableList(mFilterNamespaces); 178 } 179 180 /** Returns the ranking strategy. */ 181 @SuggestionRankingStrategy getRankingStrategy()182 public int getRankingStrategy() { 183 return mRankingStrategy; 184 } 185 186 /** 187 * Returns the list of schema to search the suggestion over. 188 * 189 * <p>If empty, will search over all schemas. 190 */ getFilterSchemas()191 public @NonNull List<String> getFilterSchemas() { 192 if (mFilterSchemas == null) { 193 return Collections.emptyList(); 194 } 195 return Collections.unmodifiableList(mFilterSchemas); 196 } 197 198 /** 199 * Returns the map of schema and target properties to search over. 200 * 201 * <p>The keys of the returned map are schema types, and the values are the target property path 202 * in that schema to search over. 203 * 204 * <p>If {@link Builder#addFilterPropertyPaths} was never called, returns an empty map. In this 205 * case AppSearch will search over all schemas and properties. 206 * 207 * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned 208 * by this function, rather than calling it multiple times. 209 */ 210 @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_PROPERTIES) getFilterProperties()211 public @NonNull Map<String, List<String>> getFilterProperties() { 212 Set<String> schemas = mFilterProperties.keySet(); 213 Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size()); 214 for (String schema : schemas) { 215 typePropertyPathsMap.put(schema, Preconditions.checkNotNull( 216 mFilterProperties.getStringArrayList(schema))); 217 } 218 return typePropertyPathsMap; 219 } 220 221 /** 222 * Returns the map of namespace and target document ids to search over. 223 * 224 * <p>The keys of the returned map are namespaces, and the values are the target document ids 225 * in that namespace to search over. 226 * 227 * <p>If {@link Builder#addFilterDocumentIds} was never called, returns an empty map. In this 228 * case AppSearch will search over all namespace and document ids. 229 * 230 * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned 231 * by this function, rather than calling it multiple times. 232 */ getFilterDocumentIds()233 public @NonNull Map<String, List<String>> getFilterDocumentIds() { 234 Set<String> namespaces = mFilterDocumentIds.keySet(); 235 Map<String, List<String>> documentIdsMap = new ArrayMap<>(namespaces.size()); 236 for (String namespace : namespaces) { 237 documentIdsMap.put(namespace, Preconditions.checkNotNull( 238 mFilterDocumentIds.getStringArrayList(namespace))); 239 } 240 return documentIdsMap; 241 } 242 243 /** 244 * Returns the list of String parameters that can be referenced in the query through the 245 * "getSearchStringParameter({index})" function. 246 * 247 * @see AppSearchSession#search 248 */ 249 @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS) getSearchStringParameters()250 public @NonNull List<String> getSearchStringParameters() { 251 return mSearchStringParameters; 252 } 253 254 /** Builder for {@link SearchSuggestionSpec objects}. */ 255 public static final class Builder { 256 private ArrayList<String> mNamespaces = new ArrayList<>(); 257 private ArrayList<String> mSchemas = new ArrayList<>(); 258 private Bundle mTypePropertyFilters = new Bundle(); 259 private Bundle mDocumentIds = new Bundle(); 260 private final int mTotalResultCount; 261 @SuggestionRankingStrategy private int mRankingStrategy = 262 SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT; 263 private List<String> mSearchStringParameters = new ArrayList<>(); 264 private boolean mBuilt = false; 265 266 /** 267 * Creates an {@link SearchSuggestionSpec.Builder} object. 268 * 269 * @param maximumResultCount Sets the maximum number of suggestion in the returned object. 270 */ Builder(@ntRangefrom = 1) int maximumResultCount)271 public Builder(@IntRange(from = 1) int maximumResultCount) { 272 Preconditions.checkArgument(maximumResultCount >= 1, 273 "maximumResultCount must be positive."); 274 mTotalResultCount = maximumResultCount; 275 } 276 277 /** 278 * Adds a namespace filter to {@link SearchSuggestionSpec} Entry. Only search for 279 * suggestions that has documents under the specified namespaces. 280 * 281 * <p>If unset, the query will search over all namespaces. 282 */ 283 @CanIgnoreReturnValue addFilterNamespaces(@onNull String... namespaces)284 public @NonNull Builder addFilterNamespaces(@NonNull String... namespaces) { 285 Preconditions.checkNotNull(namespaces); 286 resetIfBuilt(); 287 return addFilterNamespaces(Arrays.asList(namespaces)); 288 } 289 290 /** 291 * Adds a namespace filter to {@link SearchSuggestionSpec} Entry. Only search for 292 * suggestions that has documents under the specified namespaces. 293 * 294 * <p>If unset, the query will search over all namespaces. 295 */ 296 @CanIgnoreReturnValue addFilterNamespaces(@onNull Collection<String> namespaces)297 public @NonNull Builder addFilterNamespaces(@NonNull Collection<String> namespaces) { 298 Preconditions.checkNotNull(namespaces); 299 resetIfBuilt(); 300 mNamespaces.addAll(namespaces); 301 return this; 302 } 303 304 /** 305 * Sets ranking strategy for suggestion results. 306 * 307 * <p>The default value {@link #SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT} will be used if 308 * this method is never called. 309 */ 310 @CanIgnoreReturnValue setRankingStrategy(@uggestionRankingStrategy int rankingStrategy)311 public @NonNull Builder setRankingStrategy(@SuggestionRankingStrategy int rankingStrategy) { 312 Preconditions.checkArgumentInRange(rankingStrategy, 313 SUGGESTION_RANKING_STRATEGY_DOCUMENT_COUNT, SUGGESTION_RANKING_STRATEGY_NONE, 314 "Suggestion ranking strategy"); 315 resetIfBuilt(); 316 mRankingStrategy = rankingStrategy; 317 return this; 318 } 319 320 /** 321 * Adds a schema filter to {@link SearchSuggestionSpec} Entry. Only search for 322 * suggestions that has documents under the specified schema. 323 * 324 * <p>If unset, the query will search over all schema. 325 */ 326 @CanIgnoreReturnValue addFilterSchemas(@onNull String... schemaTypes)327 public @NonNull Builder addFilterSchemas(@NonNull String... schemaTypes) { 328 Preconditions.checkNotNull(schemaTypes); 329 resetIfBuilt(); 330 return addFilterSchemas(Arrays.asList(schemaTypes)); 331 } 332 333 /** 334 * Adds a schema filter to {@link SearchSuggestionSpec} Entry. Only search for 335 * suggestions that has documents under the specified schema. 336 * 337 * <p>If unset, the query will search over all schema. 338 */ 339 @CanIgnoreReturnValue addFilterSchemas(@onNull Collection<String> schemaTypes)340 public @NonNull Builder addFilterSchemas(@NonNull Collection<String> schemaTypes) { 341 Preconditions.checkNotNull(schemaTypes); 342 resetIfBuilt(); 343 mSchemas.addAll(schemaTypes); 344 return this; 345 } 346 347 // @exportToFramework:startStrip() 348 349 /** 350 * Adds a schema filter to {@link SearchSuggestionSpec} Entry. Only search for 351 * suggestions that has documents under the specified schema. 352 * 353 * <p>If unset, the query will search over all schema. 354 * 355 * <p>Merged list available from {@link #getFilterSchemas()}. 356 * 357 * @param documentClasses classes annotated with {@link Document}. 358 */ 359 @SuppressLint("MissingGetterMatchingBuilder") 360 @CanIgnoreReturnValue addFilterDocumentClasses( @onNull java.lang.Class<?>.... documentClasses)361 public @NonNull Builder addFilterDocumentClasses( 362 @NonNull java.lang.Class<?>... documentClasses) 363 throws AppSearchException { 364 Preconditions.checkNotNull(documentClasses); 365 resetIfBuilt(); 366 return addFilterDocumentClasses(Arrays.asList(documentClasses)); 367 } 368 // @exportToFramework:endStrip() 369 370 // @exportToFramework:startStrip() 371 372 /** 373 * Adds the Schema names of given document classes to the Schema type filter of 374 * {@link SearchSuggestionSpec} Entry. Only search for suggestions that has documents 375 * under the specified schema. 376 * 377 * <p>If unset, the query will search over all schema. 378 * 379 * <p>Merged list available from {@link #getFilterSchemas()}. 380 * 381 * @param documentClasses classes annotated with {@link Document}. 382 */ 383 @SuppressLint("MissingGetterMatchingBuilder") 384 @CanIgnoreReturnValue addFilterDocumentClasses( @onNull Collection<? extends java.lang.Class<?>> documentClasses)385 public @NonNull Builder addFilterDocumentClasses( 386 @NonNull Collection<? extends java.lang.Class<?>> documentClasses) 387 throws AppSearchException { 388 Preconditions.checkNotNull(documentClasses); 389 resetIfBuilt(); 390 List<String> schemas = new ArrayList<>(documentClasses.size()); 391 DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance(); 392 for (java.lang.Class<?> documentClass : documentClasses) { 393 DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass); 394 schemas.add(factory.getSchemaName()); 395 } 396 addFilterSchemas(schemas); 397 return this; 398 } 399 // @exportToFramework:endStrip() 400 401 /** 402 * Adds property paths for the specified type to the property filter of 403 * {@link SearchSuggestionSpec} Entry. Only search for suggestions that has content under 404 * the specified property. If property paths are added for a type, then only the 405 * properties referred to will be retrieved for results of that type. 406 * 407 * <p> If a property path that is specified isn't present in a result, it will be ignored 408 * for that result. Property paths cannot be null. 409 * 410 * <p>If no property paths are added for a particular type, then all properties of 411 * results of that type will be retrieved. 412 * 413 * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc. 414 * 415 * @param schema the {@link AppSearchSchema} that contains the target properties 416 * @param propertyPaths The String version of {@link PropertyPath}. A dot-delimited 417 * sequence of property names indicating which property in the 418 * document these snippets correspond to. 419 */ 420 @CanIgnoreReturnValue 421 @RequiresFeature( 422 enforcement = "androidx.appsearch.app.Features#isFeatureSupported", 423 name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) 424 @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_PROPERTIES) addFilterProperties(@onNull String schema, @NonNull Collection<String> propertyPaths)425 public @NonNull Builder addFilterProperties(@NonNull String schema, 426 @NonNull Collection<String> propertyPaths) { 427 Preconditions.checkNotNull(schema); 428 Preconditions.checkNotNull(propertyPaths); 429 resetIfBuilt(); 430 ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size()); 431 for (String propertyPath : propertyPaths) { 432 Preconditions.checkNotNull(propertyPath); 433 propertyPathsArrayList.add(propertyPath); 434 } 435 mTypePropertyFilters.putStringArrayList(schema, propertyPathsArrayList); 436 return this; 437 } 438 439 /** 440 * Adds property paths for the specified type to the property filter of 441 * {@link SearchSuggestionSpec} Entry. Only search for suggestions that has content under 442 * the specified property. If property paths are added for a type, then only the 443 * properties referred to will be retrieved for results of that type. 444 * 445 * <p> If a property path that is specified isn't present in a result, it will be ignored 446 * for that result. Property paths cannot be null. 447 * 448 * <p>If no property paths are added for a particular type, then all properties of 449 * results of that type will be retrieved. 450 * 451 * @param schema the {@link AppSearchSchema} that contains the target properties 452 * @param propertyPaths The {@link PropertyPath} to search suggestion over 453 */ 454 @CanIgnoreReturnValue 455 // Getter method is getFilterProperties 456 @SuppressLint("MissingGetterMatchingBuilder") 457 @RequiresFeature( 458 enforcement = "androidx.appsearch.app.Features#isFeatureSupported", 459 name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) 460 @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_FILTER_PROPERTIES) addFilterPropertyPaths(@onNull String schema, @NonNull Collection<PropertyPath> propertyPaths)461 public @NonNull Builder addFilterPropertyPaths(@NonNull String schema, 462 @NonNull Collection<PropertyPath> propertyPaths) { 463 Preconditions.checkNotNull(schema); 464 Preconditions.checkNotNull(propertyPaths); 465 ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size()); 466 for (PropertyPath propertyPath : propertyPaths) { 467 propertyPathsArrayList.add(propertyPath.toString()); 468 } 469 return addFilterProperties(schema, propertyPathsArrayList); 470 } 471 472 473 // @exportToFramework:startStrip() 474 /** 475 * Adds property paths for the specified type to the property filter of 476 * {@link SearchSuggestionSpec} Entry. Only search for suggestions that has content under 477 * the specified property. If property paths are added for a type, then only the 478 * properties referred to will be retrieved for results of that type. 479 * 480 * <p> If a property path that is specified isn't present in a result, it will be ignored 481 * for that result. Property paths cannot be null. 482 * 483 * <p>If no property paths are added for a particular type, then all properties of 484 * results of that type will be retrieved. 485 * 486 * @param documentClass class annotated with {@link Document}. 487 * @param propertyPaths The String version of {@link PropertyPath}. A 488 * {@code dot-delimited sequence of property names indicating which property in the 489 * document these snippets correspond to. 490 */ 491 @RequiresFeature( 492 enforcement = "androidx.appsearch.app.Features#isFeatureSupported", 493 name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) addFilterProperties(@onNull java.lang.Class<?> documentClass, @NonNull Collection<String> propertyPaths)494 public @NonNull Builder addFilterProperties(@NonNull java.lang.Class<?> documentClass, 495 @NonNull Collection<String> propertyPaths) throws AppSearchException { 496 Preconditions.checkNotNull(documentClass); 497 Preconditions.checkNotNull(propertyPaths); 498 resetIfBuilt(); 499 DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance(); 500 DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass); 501 return addFilterProperties(factory.getSchemaName(), propertyPaths); 502 } 503 // @exportToFramework:endStrip() 504 505 // @exportToFramework:startStrip() 506 /** 507 * Adds property paths for the specified type to the property filter of 508 * {@link SearchSuggestionSpec} Entry. Only search for suggestions that has content under 509 * the specified property. If property paths are added for a type, then only the 510 * properties referred to will be retrieved for results of that type. 511 * 512 * <p> If a property path that is specified isn't present in a result, it will be ignored 513 * for that result. Property paths cannot be null. 514 * 515 * <p>If no property paths are added for a particular type, then all properties of 516 * results of that type will be retrieved. 517 * 518 * @param documentClass class annotated with {@link Document}. 519 * @param propertyPaths The {@link PropertyPath} to search suggestion over 520 */ 521 // Getter method is getFilterProperties 522 @SuppressLint("MissingGetterMatchingBuilder") 523 @RequiresFeature( 524 enforcement = "androidx.appsearch.app.Features#isFeatureSupported", 525 name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES) addFilterPropertyPaths(@onNull java.lang.Class<?> documentClass, @NonNull Collection<PropertyPath> propertyPaths)526 public @NonNull Builder addFilterPropertyPaths(@NonNull java.lang.Class<?> documentClass, 527 @NonNull Collection<PropertyPath> propertyPaths) throws AppSearchException { 528 Preconditions.checkNotNull(documentClass); 529 Preconditions.checkNotNull(propertyPaths); 530 resetIfBuilt(); 531 DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance(); 532 DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass); 533 return addFilterPropertyPaths(factory.getSchemaName(), propertyPaths); 534 } 535 // @exportToFramework:endStrip() 536 537 /** 538 * Adds a document ID filter to {@link SearchSuggestionSpec} Entry. Only search for 539 * suggestions in the given specified documents. 540 * 541 * <p>If unset, the query will search over all documents. 542 */ 543 @CanIgnoreReturnValue addFilterDocumentIds(@onNull String namespace, @NonNull String... documentIds)544 public @NonNull Builder addFilterDocumentIds(@NonNull String namespace, 545 @NonNull String... documentIds) { 546 Preconditions.checkNotNull(namespace); 547 Preconditions.checkNotNull(documentIds); 548 resetIfBuilt(); 549 return addFilterDocumentIds(namespace, Arrays.asList(documentIds)); 550 } 551 552 /** 553 * Adds a document ID filter to {@link SearchSuggestionSpec} Entry. Only search for 554 * suggestions in the given specified documents. 555 * 556 * <p>If unset, the query will search over all documents. 557 */ 558 @CanIgnoreReturnValue addFilterDocumentIds(@onNull String namespace, @NonNull Collection<String> documentIds)559 public @NonNull Builder addFilterDocumentIds(@NonNull String namespace, 560 @NonNull Collection<String> documentIds) { 561 Preconditions.checkNotNull(namespace); 562 Preconditions.checkNotNull(documentIds); 563 resetIfBuilt(); 564 ArrayList<String> documentIdList = new ArrayList<>(documentIds.size()); 565 for (String documentId : documentIds) { 566 documentIdList.add(Preconditions.checkNotNull(documentId)); 567 } 568 mDocumentIds.putStringArrayList(namespace, documentIdList); 569 return this; 570 } 571 572 /** 573 * Adds Strings to the list of String parameters that can be referenced in the query through 574 * the "getSearchStringParameter({index})" function. 575 * 576 * @see AppSearchSession#search 577 */ 578 @CanIgnoreReturnValue 579 @RequiresFeature( 580 enforcement = "androidx.appsearch.app.Features#isFeatureSupported", 581 name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) 582 @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS) addSearchStringParameters( @onNull String... searchStringParameters)583 public @NonNull Builder addSearchStringParameters( 584 @NonNull String... searchStringParameters) { 585 Preconditions.checkNotNull(searchStringParameters); 586 resetIfBuilt(); 587 return addSearchStringParameters(Arrays.asList(searchStringParameters)); 588 } 589 590 /** 591 * Adds Strings to the list of String parameters that can be referenced in the query through 592 * the "getSearchStringParameter({index})" function. 593 * 594 * @see AppSearchSession#search 595 */ 596 @CanIgnoreReturnValue 597 @RequiresFeature( 598 enforcement = "androidx.appsearch.app.Features#isFeatureSupported", 599 name = Features.SEARCH_SPEC_SEARCH_STRING_PARAMETERS) 600 @FlaggedApi(Flags.FLAG_ENABLE_SEARCH_SPEC_SEARCH_STRING_PARAMETERS) addSearchStringParameters( @onNull List<String> searchStringParameters)601 public @NonNull Builder addSearchStringParameters( 602 @NonNull List<String> searchStringParameters) { 603 Preconditions.checkNotNull(searchStringParameters); 604 resetIfBuilt(); 605 mSearchStringParameters.addAll(searchStringParameters); 606 return this; 607 } 608 609 /** Constructs a new {@link SearchSpec} from the contents of this builder. */ build()610 public @NonNull SearchSuggestionSpec build() { 611 if (!mSchemas.isEmpty()) { 612 Set<String> schemaFilter = new ArraySet<>(mSchemas); 613 for (String schema : mTypePropertyFilters.keySet()) { 614 if (!schemaFilter.contains(schema)) { 615 throw new IllegalStateException( 616 "The schema: " + schema + " exists in the property filter but " 617 + "doesn't exist in the schema filter."); 618 } 619 } 620 } 621 if (!mNamespaces.isEmpty()) { 622 Set<String> namespaceFilter = new ArraySet<>(mNamespaces); 623 for (String namespace : mDocumentIds.keySet()) { 624 if (!namespaceFilter.contains(namespace)) { 625 throw new IllegalStateException( 626 "The namespace: " + namespace + " exists in the document id " 627 + "filter but doesn't exist in the namespace filter."); 628 } 629 } 630 } 631 mBuilt = true; 632 return new SearchSuggestionSpec( 633 mNamespaces, 634 mSchemas, 635 mTypePropertyFilters, 636 mDocumentIds, 637 mRankingStrategy, 638 mTotalResultCount, 639 mSearchStringParameters); 640 } 641 resetIfBuilt()642 private void resetIfBuilt() { 643 if (mBuilt) { 644 mNamespaces = new ArrayList<>(mNamespaces); 645 mSchemas = new ArrayList<>(mSchemas); 646 mTypePropertyFilters = BundleUtil.deepCopy(mTypePropertyFilters); 647 mDocumentIds = BundleUtil.deepCopy(mDocumentIds); 648 mSearchStringParameters = new ArrayList<>(mSearchStringParameters); 649 mBuilt = false; 650 } 651 } 652 } 653 654 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 655 @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) 656 @Override writeToParcel(@onNull Parcel dest, int flags)657 public void writeToParcel(@NonNull Parcel dest, int flags) { 658 SearchSuggestionSpecCreator.writeToParcel(this, dest, flags); 659 } 660 } 661