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.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.SuppressLint; 24 import android.app.appsearch.annotation.CanIgnoreReturnValue; 25 import android.app.appsearch.exceptions.AppSearchException; 26 import android.app.appsearch.exceptions.IllegalSchemaException; 27 import android.app.appsearch.safeparcel.AbstractSafeParcelable; 28 import android.app.appsearch.safeparcel.PropertyConfigParcel; 29 import android.app.appsearch.safeparcel.PropertyConfigParcel.DocumentIndexingConfigParcel; 30 import android.app.appsearch.safeparcel.PropertyConfigParcel.JoinableConfigParcel; 31 import android.app.appsearch.safeparcel.PropertyConfigParcel.StringIndexingConfigParcel; 32 import android.app.appsearch.safeparcel.SafeParcelable; 33 import android.app.appsearch.util.IndentingStringBuilder; 34 import android.os.Bundle; 35 import android.os.Parcel; 36 import android.os.Parcelable; 37 import android.util.ArraySet; 38 39 import com.android.appsearch.flags.Flags; 40 import com.android.internal.util.Preconditions; 41 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collection; 47 import java.util.Collections; 48 import java.util.LinkedHashSet; 49 import java.util.List; 50 import java.util.Objects; 51 import java.util.Set; 52 53 /** 54 * The AppSearch Schema for a particular type of document. 55 * 56 * <p>For example, an e-mail message or a music recording could be a schema type. 57 * 58 * <p>The schema consists of type information, properties, and config (like tokenization type). 59 * 60 * @see AppSearchSession#setSchema 61 */ 62 @SafeParcelable.Class(creator = "AppSearchSchemaCreator") 63 @SuppressWarnings("HiddenSuperclass") 64 public final class AppSearchSchema extends AbstractSafeParcelable { 65 66 @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) 67 @NonNull 68 public static final Parcelable.Creator<AppSearchSchema> CREATOR = new AppSearchSchemaCreator(); 69 70 @Field(id = 1, getter = "getSchemaType") 71 private final String mSchemaType; 72 73 @Field(id = 2) 74 final List<PropertyConfigParcel> mPropertyConfigParcels; 75 76 @Field(id = 3, getter = "getParentTypes") 77 private final List<String> mParentTypes; 78 79 @Field(id = 4, getter = "getDescription") 80 private final String mDescription; 81 82 @Constructor AppSearchSchema( @aramid = 1) @onNull String schemaType, @Param(id = 2) @NonNull List<PropertyConfigParcel> propertyConfigParcels, @Param(id = 3) @NonNull List<String> parentTypes, @Param(id = 4) @NonNull String description)83 AppSearchSchema( 84 @Param(id = 1) @NonNull String schemaType, 85 @Param(id = 2) @NonNull List<PropertyConfigParcel> propertyConfigParcels, 86 @Param(id = 3) @NonNull List<String> parentTypes, 87 @Param(id = 4) @NonNull String description) { 88 mSchemaType = Objects.requireNonNull(schemaType); 89 mPropertyConfigParcels = Objects.requireNonNull(propertyConfigParcels); 90 mParentTypes = Objects.requireNonNull(parentTypes); 91 mDescription = Objects.requireNonNull(description); 92 } 93 94 @Override 95 @NonNull toString()96 public String toString() { 97 IndentingStringBuilder stringBuilder = new IndentingStringBuilder(); 98 appendAppSearchSchemaString(stringBuilder); 99 return stringBuilder.toString(); 100 } 101 102 /** 103 * Appends a debugging string for the {@link AppSearchSchema} instance to the given string 104 * builder. 105 * 106 * @param builder the builder to append to. 107 */ appendAppSearchSchemaString(@onNull IndentingStringBuilder builder)108 private void appendAppSearchSchemaString(@NonNull IndentingStringBuilder builder) { 109 Objects.requireNonNull(builder); 110 111 builder.append("{\n"); 112 builder.increaseIndentLevel(); 113 builder.append("schemaType: \"").append(getSchemaType()).append("\",\n"); 114 builder.append("description: \"").append(getDescription()).append("\",\n"); 115 builder.append("properties: [\n"); 116 117 AppSearchSchema.PropertyConfig[] sortedProperties = 118 getProperties().toArray(new AppSearchSchema.PropertyConfig[0]); 119 Arrays.sort(sortedProperties, (o1, o2) -> o1.getName().compareTo(o2.getName())); 120 121 for (int i = 0; i < sortedProperties.length; i++) { 122 AppSearchSchema.PropertyConfig propertyConfig = sortedProperties[i]; 123 builder.increaseIndentLevel(); 124 propertyConfig.appendPropertyConfigString(builder); 125 if (i != sortedProperties.length - 1) { 126 builder.append(",\n"); 127 } 128 builder.decreaseIndentLevel(); 129 } 130 131 builder.append("\n"); 132 builder.append("]\n"); 133 builder.decreaseIndentLevel(); 134 builder.append("}"); 135 } 136 137 /** Returns the name of this schema type, such as Email. */ 138 @NonNull getSchemaType()139 public String getSchemaType() { 140 return mSchemaType; 141 } 142 143 /** 144 * Returns a natural language description of this schema type. 145 * 146 * <p>Ex. The description for an Email type could be "A type of electronic message". 147 * 148 * <p>This information is purely to help apps consuming this type to understand its semantic 149 * meaning. This field has no effect in AppSearch - it is just stored with the AppSearchSchema. 150 * If {@link Builder#setDescription} is uncalled, then this method will return an empty string. 151 */ 152 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 153 @NonNull getDescription()154 public String getDescription() { 155 return mDescription; 156 } 157 158 /** 159 * Returns the list of {@link PropertyConfig}s that are part of this schema. 160 * 161 * <p>This method creates a new list when called. 162 */ 163 @NonNull 164 @SuppressWarnings({"MixedMutabilityReturnType"}) getProperties()165 public List<PropertyConfig> getProperties() { 166 if (mPropertyConfigParcels.isEmpty()) { 167 return Collections.emptyList(); 168 } 169 List<PropertyConfig> ret = new ArrayList<>(mPropertyConfigParcels.size()); 170 for (int i = 0; i < mPropertyConfigParcels.size(); i++) { 171 ret.add(PropertyConfig.fromParcel(mPropertyConfigParcels.get(i))); 172 } 173 return ret; 174 } 175 176 /** Returns the list of parent types of this schema for polymorphism. */ 177 @FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES) 178 @NonNull getParentTypes()179 public List<String> getParentTypes() { 180 return Collections.unmodifiableList(mParentTypes); 181 } 182 183 @Override equals(@ullable Object other)184 public boolean equals(@Nullable Object other) { 185 if (this == other) { 186 return true; 187 } 188 if (!(other instanceof AppSearchSchema)) { 189 return false; 190 } 191 AppSearchSchema otherSchema = (AppSearchSchema) other; 192 if (!getSchemaType().equals(otherSchema.getSchemaType())) { 193 return false; 194 } 195 if (!getDescription().equals(otherSchema.getDescription())) { 196 return false; 197 } 198 if (!getParentTypes().equals(otherSchema.getParentTypes())) { 199 return false; 200 } 201 return getProperties().equals(otherSchema.getProperties()); 202 } 203 204 @Override hashCode()205 public int hashCode() { 206 return Objects.hash(getSchemaType(), getProperties(), getParentTypes(), getDescription()); 207 } 208 209 @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) 210 @Override writeToParcel(@onNull Parcel dest, int flags)211 public void writeToParcel(@NonNull Parcel dest, int flags) { 212 AppSearchSchemaCreator.writeToParcel(this, dest, flags); 213 } 214 215 /** Builder for {@link AppSearchSchema objects}. */ 216 public static final class Builder { 217 private final String mSchemaType; 218 private String mDescription = ""; 219 private ArrayList<PropertyConfigParcel> mPropertyConfigParcels = new ArrayList<>(); 220 private LinkedHashSet<String> mParentTypes = new LinkedHashSet<>(); 221 private final Set<String> mPropertyNames = new ArraySet<>(); 222 private boolean mBuilt = false; 223 224 /** Creates a new {@link AppSearchSchema.Builder}. */ Builder(@onNull String schemaType)225 public Builder(@NonNull String schemaType) { 226 mSchemaType = Objects.requireNonNull(schemaType); 227 } 228 229 /** 230 * Sets a natural language description of this schema type. 231 * 232 * <p>For more details about the description field, see {@link 233 * AppSearchSchema#getDescription}. 234 */ 235 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 236 @CanIgnoreReturnValue 237 @NonNull setDescription(@onNull String description)238 public AppSearchSchema.Builder setDescription(@NonNull String description) { 239 Objects.requireNonNull(description); 240 resetIfBuilt(); 241 mDescription = description; 242 return this; 243 } 244 245 /** Adds a property to the given type. */ 246 @CanIgnoreReturnValue 247 @NonNull addProperty(@onNull PropertyConfig propertyConfig)248 public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) { 249 Objects.requireNonNull(propertyConfig); 250 resetIfBuilt(); 251 String name = propertyConfig.getName(); 252 if (!mPropertyNames.add(name)) { 253 throw new IllegalSchemaException("Property defined more than once: " + name); 254 } 255 mPropertyConfigParcels.add(propertyConfig.mPropertyConfigParcel); 256 return this; 257 } 258 259 /** 260 * Adds a parent type to the given type for polymorphism, so that the given type will be 261 * considered as a subtype of {@code parentSchemaType}. 262 * 263 * <p>Subtype relations are automatically considered transitive, so callers are only 264 * required to provide direct parents. Specifically, if T1 <: T2 and T2 <: T3 are 265 * known, then T1 <: T3 will be inferred automatically, where <: is the subtype 266 * symbol. 267 * 268 * <p>Polymorphism is currently supported in the following ways: 269 * 270 * <ul> 271 * <li>Search filters on a parent type will automatically be extended to the child types 272 * as well. For example, if Artist <: Person, then a search with a filter on type 273 * Person (by calling {@link SearchSpec.Builder#addFilterSchemas}) will also include 274 * documents of type Artist in the search result. 275 * <li>In the projection API, the property paths to project specified for a parent type 276 * will automatically be extended to the child types as well. If both a parent type 277 * and one of its child type are specified in the projection API, the parent type's 278 * paths will be merged into the child's. For more details on projection, see {@link 279 * SearchSpec.Builder#addProjection}. 280 * <li>A document property defined as type U is allowed to be set with a document of type 281 * T, as long as T <: U, but note that index will only be based on the defined 282 * type, which is U. For example, consider a document of type "Company" with a 283 * repeated "employees" field of type "Person". We can add employees of either type 284 * "Person" or type "Artist" or both to this property, as long as "Artist" is a 285 * subtype of "Person". However, the index of the "employees" property will be based 286 * on what's defined in "Person", even for an added document of type "Artist". 287 * </ul> 288 * 289 * <p>Subtypes must meet the following requirements. A violation of the requirements will 290 * cause {@link AppSearchSession#setSchema} to throw an {@link AppSearchException} with the 291 * result code of {@link AppSearchResult#RESULT_INVALID_ARGUMENT}. Consider a type Artist 292 * and a type Person, and Artist claims to be a subtype of Person, then: 293 * 294 * <ul> 295 * <li>Every property in Person must have a corresponding property in Artist with the same 296 * name. 297 * <li>Every non-document property in Person must have the same type as the type of the 298 * corresponding property in Artist. For example, if "age" is an integer property in 299 * Person, then "age" must also be an integer property in Artist, instead of a string. 300 * <li>The schema type of every document property in Artist must be a subtype of the 301 * schema type of the corresponding document property in Person, if such a property 302 * exists in Person. For example, if "awards" is a document property of type Award in 303 * Person, then the type of the "awards" property in Artist must be a subtype of 304 * Award, say ArtAward. Note that every type is a subtype of itself. 305 * <li>Every property in Artist must have a cardinality stricter than or equal to the 306 * cardinality of the corresponding property in Person, if such a property exists in 307 * Person. For example, if "awards" is a property in Person of cardinality OPTIONAL, 308 * then the cardinality of the "awards" property in Artist can only be REQUIRED or 309 * OPTIONAL. Rule: REQUIRED < OPTIONAL < REPEATED. 310 * <li>There are no other enforcements on the corresponding properties in Artist, such as 311 * index type, tokenizer type, etc. These settings can be safely overridden. 312 * </ul> 313 * 314 * <p>A type can be defined to have multiple parents, but it must be compatible with each of 315 * its parents based on the above rules. For example, if LocalBusiness is defined as a 316 * subtype of both Place and Organization, then the compatibility of LocalBusiness with 317 * Place and the compatibility of LocalBusiness with Organization will both be checked. 318 */ 319 @CanIgnoreReturnValue 320 @NonNull addParentType(@onNull String parentSchemaType)321 public AppSearchSchema.Builder addParentType(@NonNull String parentSchemaType) { 322 Objects.requireNonNull(parentSchemaType); 323 resetIfBuilt(); 324 mParentTypes.add(parentSchemaType); 325 return this; 326 } 327 328 /** Constructs a new {@link AppSearchSchema} from the contents of this builder. */ 329 @NonNull build()330 public AppSearchSchema build() { 331 mBuilt = true; 332 return new AppSearchSchema( 333 mSchemaType, 334 mPropertyConfigParcels, 335 new ArrayList<>(mParentTypes), 336 mDescription); 337 } 338 resetIfBuilt()339 private void resetIfBuilt() { 340 if (mBuilt) { 341 mPropertyConfigParcels = new ArrayList<>(mPropertyConfigParcels); 342 mParentTypes = new LinkedHashSet<>(mParentTypes); 343 mBuilt = false; 344 } 345 } 346 } 347 348 /** 349 * Common configuration for a single property (field) in a Document. 350 * 351 * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be a 352 * property. 353 */ 354 public abstract static class PropertyConfig { 355 /** 356 * Physical data-types of the contents of the property. 357 * 358 * <p>NOTE: The integer values of these constants must match the proto enum constants in 359 * com.google.android.icing.proto.PropertyConfigProto.DataType.Code. 360 * 361 * @hide 362 */ 363 @IntDef( 364 value = { 365 DATA_TYPE_STRING, 366 DATA_TYPE_LONG, 367 DATA_TYPE_DOUBLE, 368 DATA_TYPE_BOOLEAN, 369 DATA_TYPE_BYTES, 370 DATA_TYPE_DOCUMENT, 371 DATA_TYPE_EMBEDDING, 372 }) 373 @Retention(RetentionPolicy.SOURCE) 374 public @interface DataType {} 375 376 /** 377 * Constant value for String data type. 378 * 379 * @hide 380 */ 381 public static final int DATA_TYPE_STRING = 1; 382 383 /** 384 * Constant value for Long data type. 385 * 386 * @hide 387 */ 388 public static final int DATA_TYPE_LONG = 2; 389 390 /** 391 * Constant value for Double data type. 392 * 393 * @hide 394 */ 395 public static final int DATA_TYPE_DOUBLE = 3; 396 397 /** 398 * Constant value for Boolean data type. 399 * 400 * @hide 401 */ 402 public static final int DATA_TYPE_BOOLEAN = 4; 403 404 /** 405 * Unstructured BLOB. 406 * 407 * @hide 408 */ 409 public static final int DATA_TYPE_BYTES = 5; 410 411 /** 412 * Indicates that the property is itself a {@link GenericDocument}, making it part of a 413 * hierarchical schema. Any property using this DataType MUST have a valid {@link 414 * PropertyConfig#getSchemaType}. 415 * 416 * @hide 417 */ 418 public static final int DATA_TYPE_DOCUMENT = 6; 419 420 /** 421 * Indicates that the property is an {@link EmbeddingVector}. 422 * 423 * @hide 424 */ 425 public static final int DATA_TYPE_EMBEDDING = 7; 426 427 /** 428 * The cardinality of the property (whether it is required, optional or repeated). 429 * 430 * <p>NOTE: The integer values of these constants must match the proto enum constants in 431 * com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code. 432 * 433 * @hide 434 */ 435 @IntDef( 436 value = { 437 CARDINALITY_REPEATED, 438 CARDINALITY_OPTIONAL, 439 CARDINALITY_REQUIRED, 440 }) 441 @Retention(RetentionPolicy.SOURCE) 442 public @interface Cardinality {} 443 444 /** Any number of items (including zero) [0...*]. */ 445 public static final int CARDINALITY_REPEATED = 1; 446 447 /** Zero or one value [0,1]. */ 448 public static final int CARDINALITY_OPTIONAL = 2; 449 450 /** Exactly one value [1]. */ 451 public static final int CARDINALITY_REQUIRED = 3; 452 453 final PropertyConfigParcel mPropertyConfigParcel; 454 PropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)455 PropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) { 456 mPropertyConfigParcel = Objects.requireNonNull(propertyConfigParcel); 457 } 458 459 @Override 460 @NonNull toString()461 public String toString() { 462 IndentingStringBuilder stringBuilder = new IndentingStringBuilder(); 463 appendPropertyConfigString(stringBuilder); 464 return stringBuilder.toString(); 465 } 466 467 /** 468 * Appends a debug string for the {@link AppSearchSchema.PropertyConfig} instance to the 469 * given string builder. 470 * 471 * @param builder the builder to append to. 472 */ appendPropertyConfigString(@onNull IndentingStringBuilder builder)473 void appendPropertyConfigString(@NonNull IndentingStringBuilder builder) { 474 Objects.requireNonNull(builder); 475 476 builder.append("{\n"); 477 builder.increaseIndentLevel(); 478 builder.append("name: \"").append(getName()).append("\",\n"); 479 builder.append("description: \"").append(getDescription()).append("\",\n"); 480 481 if (this instanceof AppSearchSchema.StringPropertyConfig) { 482 ((StringPropertyConfig) this).appendStringPropertyConfigFields(builder); 483 } else if (this instanceof AppSearchSchema.DocumentPropertyConfig) { 484 ((DocumentPropertyConfig) this).appendDocumentPropertyConfigFields(builder); 485 } else if (this instanceof AppSearchSchema.LongPropertyConfig) { 486 ((LongPropertyConfig) this).appendLongPropertyConfigFields(builder); 487 } 488 489 switch (getCardinality()) { 490 case AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED: 491 builder.append("cardinality: CARDINALITY_REPEATED,\n"); 492 break; 493 case AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL: 494 builder.append("cardinality: CARDINALITY_OPTIONAL,\n"); 495 break; 496 case AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED: 497 builder.append("cardinality: CARDINALITY_REQUIRED,\n"); 498 break; 499 default: 500 builder.append("cardinality: CARDINALITY_UNKNOWN,\n"); 501 } 502 503 switch (getDataType()) { 504 case AppSearchSchema.PropertyConfig.DATA_TYPE_STRING: 505 builder.append("dataType: DATA_TYPE_STRING,\n"); 506 break; 507 case AppSearchSchema.PropertyConfig.DATA_TYPE_LONG: 508 builder.append("dataType: DATA_TYPE_LONG,\n"); 509 break; 510 case AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE: 511 builder.append("dataType: DATA_TYPE_DOUBLE,\n"); 512 break; 513 case AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN: 514 builder.append("dataType: DATA_TYPE_BOOLEAN,\n"); 515 break; 516 case AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES: 517 builder.append("dataType: DATA_TYPE_BYTES,\n"); 518 break; 519 case AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT: 520 builder.append("dataType: DATA_TYPE_DOCUMENT,\n"); 521 break; 522 case PropertyConfig.DATA_TYPE_EMBEDDING: 523 builder.append("dataType: DATA_TYPE_EMBEDDING,\n"); 524 break; 525 default: 526 builder.append("dataType: DATA_TYPE_UNKNOWN,\n"); 527 } 528 builder.decreaseIndentLevel(); 529 builder.append("}"); 530 } 531 532 /** Returns the name of this property. */ 533 @NonNull getName()534 public String getName() { 535 return mPropertyConfigParcel.getName(); 536 } 537 538 /** 539 * Returns a natural language description of this property. 540 * 541 * <p>Ex. The description for the "homeAddress" property of a "Person" type could be "the 542 * address at which this person lives". 543 * 544 * <p>This information is purely to help apps consuming this type the semantic meaning of 545 * its properties. This field has no effect in AppSearch - it is just stored with the 546 * AppSearchSchema. If the description is not set, then this method will return an empty 547 * string. 548 */ 549 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 550 @NonNull getDescription()551 public String getDescription() { 552 return mPropertyConfigParcel.getDescription(); 553 } 554 555 /** 556 * Returns the type of data the property contains (such as string, int, bytes, etc). 557 * 558 * @hide 559 */ 560 @DataType getDataType()561 public int getDataType() { 562 return mPropertyConfigParcel.getDataType(); 563 } 564 565 /** 566 * Returns the cardinality of the property (whether it is optional, required or repeated). 567 */ 568 @Cardinality getCardinality()569 public int getCardinality() { 570 return mPropertyConfigParcel.getCardinality(); 571 } 572 573 @Override equals(@ullable Object other)574 public boolean equals(@Nullable Object other) { 575 if (this == other) { 576 return true; 577 } 578 if (!(other instanceof PropertyConfig)) { 579 return false; 580 } 581 PropertyConfig otherProperty = (PropertyConfig) other; 582 return Objects.equals(mPropertyConfigParcel, otherProperty.mPropertyConfigParcel); 583 } 584 585 @Override hashCode()586 public int hashCode() { 587 return mPropertyConfigParcel.hashCode(); 588 } 589 590 /** 591 * Converts a {@link Bundle} into a {@link PropertyConfig} depending on its internal data 592 * type. 593 * 594 * <p>The bundle is not cloned. 595 * 596 * @throws IllegalArgumentException if the bundle does no contain a recognized value in its 597 * {@code DATA_TYPE_FIELD}. 598 * @hide 599 */ 600 @NonNull fromParcel( @onNull PropertyConfigParcel propertyConfigParcel)601 public static PropertyConfig fromParcel( 602 @NonNull PropertyConfigParcel propertyConfigParcel) { 603 Objects.requireNonNull(propertyConfigParcel); 604 switch (propertyConfigParcel.getDataType()) { 605 case PropertyConfig.DATA_TYPE_STRING: 606 return new StringPropertyConfig(propertyConfigParcel); 607 case PropertyConfig.DATA_TYPE_LONG: 608 return new LongPropertyConfig(propertyConfigParcel); 609 case PropertyConfig.DATA_TYPE_DOUBLE: 610 return new DoublePropertyConfig(propertyConfigParcel); 611 case PropertyConfig.DATA_TYPE_BOOLEAN: 612 return new BooleanPropertyConfig(propertyConfigParcel); 613 case PropertyConfig.DATA_TYPE_BYTES: 614 return new BytesPropertyConfig(propertyConfigParcel); 615 case PropertyConfig.DATA_TYPE_DOCUMENT: 616 return new DocumentPropertyConfig(propertyConfigParcel); 617 case PropertyConfig.DATA_TYPE_EMBEDDING: 618 return new EmbeddingPropertyConfig(propertyConfigParcel); 619 default: 620 throw new IllegalArgumentException( 621 "Unsupported property bundle of type " 622 + propertyConfigParcel.getDataType() 623 + "; contents: " 624 + propertyConfigParcel); 625 } 626 } 627 } 628 629 /** Configuration for a property of type String in a Document. */ 630 public static final class StringPropertyConfig extends PropertyConfig { 631 /** 632 * Encapsulates the configurations on how AppSearch should query/index these terms. 633 * 634 * @hide 635 */ 636 @IntDef( 637 value = { 638 INDEXING_TYPE_NONE, 639 INDEXING_TYPE_EXACT_TERMS, 640 INDEXING_TYPE_PREFIXES, 641 }) 642 @Retention(RetentionPolicy.SOURCE) 643 public @interface IndexingType {} 644 645 /** Content in this property will not be tokenized or indexed. */ 646 public static final int INDEXING_TYPE_NONE = 0; 647 648 /** 649 * Content in this property should only be returned for queries matching the exact tokens 650 * appearing in this property. 651 * 652 * <p>For example, a property with "fool" should NOT match a query for "foo". 653 */ 654 public static final int INDEXING_TYPE_EXACT_TERMS = 1; 655 656 /** 657 * Content in this property should be returned for queries that are either exact matches or 658 * query matches of the tokens appearing in this property. 659 * 660 * <p>For example, a property with "fool" <b>should</b> match a query for "foo". 661 */ 662 public static final int INDEXING_TYPE_PREFIXES = 2; 663 664 /** 665 * Configures how tokens should be extracted from this property. 666 * 667 * <p>NOTE: The integer values of these constants must match the proto enum constants in 668 * com.google.android.icing.proto.IndexingConfig.TokenizerType.Code. 669 * 670 * @hide 671 */ 672 @IntDef( 673 value = { 674 TOKENIZER_TYPE_NONE, 675 TOKENIZER_TYPE_PLAIN, 676 TOKENIZER_TYPE_VERBATIM, 677 TOKENIZER_TYPE_RFC822 678 }) 679 @Retention(RetentionPolicy.SOURCE) 680 public @interface TokenizerType {} 681 682 /** 683 * This value indicates that no tokens should be extracted from this property. 684 * 685 * <p>It is only valid for tokenizer_type to be 'NONE' if {@link #getIndexingType} is {@link 686 * #INDEXING_TYPE_NONE}. 687 */ 688 public static final int TOKENIZER_TYPE_NONE = 0; 689 690 /** 691 * Tokenization for plain text. This value indicates that tokens should be extracted from 692 * this property based on word breaks. Segments of whitespace and punctuation are not 693 * considered tokens. 694 * 695 * <p>For example, a property with "foo bar. baz." will produce tokens for "foo", "bar" and 696 * "baz". The segments " " and "." will not be considered tokens. 697 * 698 * <p>It is only valid for tokenizer_type to be 'PLAIN' if {@link #getIndexingType} is 699 * {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}. 700 */ 701 public static final int TOKENIZER_TYPE_PLAIN = 1; 702 703 /** 704 * This value indicates that no normalization or segmentation should be applied to string 705 * values that are tokenized using this type. Therefore, the output token is equivalent to 706 * the raw string value. 707 * 708 * <p>For example, a property with "Hello, world!" will produce the token "Hello, world!", 709 * preserving punctuation and capitalization, and not creating separate tokens between the 710 * space. 711 * 712 * <p>It is only valid for tokenizer_type to be 'VERBATIM' if {@link #getIndexingType} is 713 * {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}. 714 */ 715 public static final int TOKENIZER_TYPE_VERBATIM = 2; 716 717 /** 718 * Tokenization for emails. This value indicates that tokens should be extracted from this 719 * property based on email structure. 720 * 721 * <p>For example, a property with "alex.sav@google.com" will produce tokens for "alex", 722 * "sav", "alex.sav", "google", "com", and "alexsav@google.com" 723 * 724 * <p>It is only valid for tokenizer_type to be 'RFC822' if {@link #getIndexingType} is 725 * {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}. 726 */ 727 public static final int TOKENIZER_TYPE_RFC822 = 3; 728 729 /** 730 * The joinable value type of the property. By setting the appropriate joinable value type 731 * for a property, the client can use the property for joining documents from other schema 732 * types using Search API (see {@link JoinSpec}). 733 * 734 * @hide 735 */ 736 // NOTE: The integer values of these constants must match the proto enum constants in 737 // com.google.android.icing.proto.JoinableConfig.ValueType.Code. 738 @IntDef( 739 value = { 740 JOINABLE_VALUE_TYPE_NONE, 741 JOINABLE_VALUE_TYPE_QUALIFIED_ID, 742 }) 743 @Retention(RetentionPolicy.SOURCE) 744 public @interface JoinableValueType {} 745 746 /** Content in this property is not joinable. */ 747 public static final int JOINABLE_VALUE_TYPE_NONE = 0; 748 749 /** 750 * Content in this string property will be used as a qualified id to join documents. 751 * 752 * <ul> 753 * <li>Qualified id: a unique identifier for a document, and this joinable value type is 754 * similar to primary and foreign key in relational database. See {@link 755 * android.app.appsearch.util.DocumentIdUtil} for more details. 756 * <li>Currently we only support single string joining, so it should only be used with 757 * {@link PropertyConfig#CARDINALITY_OPTIONAL} and {@link 758 * PropertyConfig#CARDINALITY_REQUIRED}. 759 * </ul> 760 */ 761 public static final int JOINABLE_VALUE_TYPE_QUALIFIED_ID = 1; 762 StringPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)763 StringPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) { 764 super(propertyConfigParcel); 765 } 766 767 /** Returns how the property is indexed. */ 768 @StringPropertyConfig.IndexingType getIndexingType()769 public int getIndexingType() { 770 StringIndexingConfigParcel indexingConfigParcel = 771 mPropertyConfigParcel.getStringIndexingConfigParcel(); 772 if (indexingConfigParcel == null) { 773 return INDEXING_TYPE_NONE; 774 } 775 776 return indexingConfigParcel.getIndexingType(); 777 } 778 779 /** Returns how this property is tokenized (split into words). */ 780 @TokenizerType getTokenizerType()781 public int getTokenizerType() { 782 StringIndexingConfigParcel indexingConfigParcel = 783 mPropertyConfigParcel.getStringIndexingConfigParcel(); 784 if (indexingConfigParcel == null) { 785 return TOKENIZER_TYPE_NONE; 786 } 787 788 return indexingConfigParcel.getTokenizerType(); 789 } 790 791 /** 792 * Returns how this property is going to be used to join documents from other schema types. 793 */ 794 @JoinableValueType getJoinableValueType()795 public int getJoinableValueType() { 796 JoinableConfigParcel joinableConfigParcel = 797 mPropertyConfigParcel.getJoinableConfigParcel(); 798 if (joinableConfigParcel == null) { 799 return JOINABLE_VALUE_TYPE_NONE; 800 } 801 802 return joinableConfigParcel.getJoinableValueType(); 803 } 804 805 /** Builder for {@link StringPropertyConfig}. */ 806 public static final class Builder { 807 private final String mPropertyName; 808 private String mDescription = ""; 809 @Cardinality private int mCardinality = CARDINALITY_OPTIONAL; 810 @StringPropertyConfig.IndexingType private int mIndexingType = INDEXING_TYPE_NONE; 811 @TokenizerType private int mTokenizerType = TOKENIZER_TYPE_NONE; 812 @JoinableValueType private int mJoinableValueType = JOINABLE_VALUE_TYPE_NONE; 813 private boolean mDeletionPropagation = false; 814 815 /** Creates a new {@link StringPropertyConfig.Builder}. */ Builder(@onNull String propertyName)816 public Builder(@NonNull String propertyName) { 817 mPropertyName = Objects.requireNonNull(propertyName); 818 } 819 820 /** 821 * Sets a natural language description of this property. 822 * 823 * <p>For more details about the description field, see {@link 824 * AppSearchSchema.PropertyConfig#getDescription}. 825 */ 826 @CanIgnoreReturnValue 827 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 828 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 829 @NonNull setDescription(@onNull String description)830 public StringPropertyConfig.Builder setDescription(@NonNull String description) { 831 mDescription = Objects.requireNonNull(description); 832 return this; 833 } 834 835 /** 836 * Sets the cardinality of the property (whether it is optional, required or repeated). 837 * 838 * <p>If this method is not called, the default cardinality is {@link 839 * PropertyConfig#CARDINALITY_OPTIONAL}. 840 */ 841 @CanIgnoreReturnValue 842 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 843 @NonNull setCardinality(@ardinality int cardinality)844 public StringPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 845 Preconditions.checkArgumentInRange( 846 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 847 mCardinality = cardinality; 848 return this; 849 } 850 851 /** 852 * Configures how a property should be indexed so that it can be retrieved by queries. 853 * 854 * <p>If this method is not called, the default indexing type is {@link 855 * StringPropertyConfig#INDEXING_TYPE_NONE}, so that it cannot be matched by queries. 856 */ 857 @CanIgnoreReturnValue 858 @NonNull setIndexingType( @tringPropertyConfig.IndexingType int indexingType)859 public StringPropertyConfig.Builder setIndexingType( 860 @StringPropertyConfig.IndexingType int indexingType) { 861 Preconditions.checkArgumentInRange( 862 indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_PREFIXES, "indexingType"); 863 mIndexingType = indexingType; 864 return this; 865 } 866 867 /** 868 * Configures how this property should be tokenized (split into words). 869 * 870 * <p>If this method is not called, the default indexing type is {@link 871 * StringPropertyConfig#TOKENIZER_TYPE_NONE}, so that it is not tokenized. 872 * 873 * <p>This method must be called with a value other than {@link 874 * StringPropertyConfig#TOKENIZER_TYPE_NONE} if the property is indexed (that is, if 875 * {@link #setIndexingType} has been called with a value other than {@link 876 * StringPropertyConfig#INDEXING_TYPE_NONE}). 877 */ 878 @CanIgnoreReturnValue 879 @NonNull setTokenizerType(@okenizerType int tokenizerType)880 public StringPropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) { 881 Preconditions.checkArgumentInRange( 882 tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_RFC822, "tokenizerType"); 883 mTokenizerType = tokenizerType; 884 return this; 885 } 886 887 /** 888 * Configures how this property should be used as a joining matcher. 889 * 890 * <p>If this method is not called, the default joinable value type is {@link 891 * StringPropertyConfig#JOINABLE_VALUE_TYPE_NONE}, so that it is not joinable. 892 * 893 * <p>At most, 64 properties can be set as joinable per schema. 894 */ 895 @CanIgnoreReturnValue 896 @NonNull setJoinableValueType( @oinableValueType int joinableValueType)897 public StringPropertyConfig.Builder setJoinableValueType( 898 @JoinableValueType int joinableValueType) { 899 Preconditions.checkArgumentInRange( 900 joinableValueType, 901 JOINABLE_VALUE_TYPE_NONE, 902 JOINABLE_VALUE_TYPE_QUALIFIED_ID, 903 "joinableValueType"); 904 mJoinableValueType = joinableValueType; 905 return this; 906 } 907 908 /** Constructs a new {@link StringPropertyConfig} from the contents of this builder. */ 909 @NonNull build()910 public StringPropertyConfig build() { 911 if (mTokenizerType == TOKENIZER_TYPE_NONE) { 912 Preconditions.checkState( 913 mIndexingType == INDEXING_TYPE_NONE, 914 "Cannot set " 915 + "TOKENIZER_TYPE_NONE with an indexing type other than " 916 + "INDEXING_TYPE_NONE."); 917 } else { 918 Preconditions.checkState( 919 mIndexingType != INDEXING_TYPE_NONE, 920 "Cannot set " + "TOKENIZER_TYPE_PLAIN with INDEXING_TYPE_NONE."); 921 } 922 if (mJoinableValueType == JOINABLE_VALUE_TYPE_QUALIFIED_ID) { 923 Preconditions.checkState( 924 mCardinality != CARDINALITY_REPEATED, 925 "Cannot set JOINABLE_VALUE_TYPE_QUALIFIED_ID with" 926 + " CARDINALITY_REPEATED."); 927 } else { 928 Preconditions.checkState( 929 !mDeletionPropagation, 930 "Cannot set deletion " 931 + "propagation without setting a joinable value type"); 932 } 933 PropertyConfigParcel.StringIndexingConfigParcel stringConfigParcel = 934 new StringIndexingConfigParcel(mIndexingType, mTokenizerType); 935 JoinableConfigParcel joinableConfigParcel = 936 new JoinableConfigParcel(mJoinableValueType, mDeletionPropagation); 937 return new StringPropertyConfig( 938 PropertyConfigParcel.createForString( 939 mPropertyName, 940 mDescription, 941 mCardinality, 942 stringConfigParcel, 943 joinableConfigParcel)); 944 } 945 } 946 947 /** 948 * Appends a debug string for the {@link StringPropertyConfig} instance to the given string 949 * builder. 950 * 951 * <p>This appends fields specific to a {@link StringPropertyConfig} instance. 952 * 953 * @param builder the builder to append to. 954 */ appendStringPropertyConfigFields(@onNull IndentingStringBuilder builder)955 void appendStringPropertyConfigFields(@NonNull IndentingStringBuilder builder) { 956 switch (getIndexingType()) { 957 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE: 958 builder.append("indexingType: INDEXING_TYPE_NONE,\n"); 959 break; 960 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS: 961 builder.append("indexingType: INDEXING_TYPE_EXACT_TERMS,\n"); 962 break; 963 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES: 964 builder.append("indexingType: INDEXING_TYPE_PREFIXES,\n"); 965 break; 966 default: 967 builder.append("indexingType: INDEXING_TYPE_UNKNOWN,\n"); 968 } 969 970 switch (getTokenizerType()) { 971 case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE: 972 builder.append("tokenizerType: TOKENIZER_TYPE_NONE,\n"); 973 break; 974 case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN: 975 builder.append("tokenizerType: TOKENIZER_TYPE_PLAIN,\n"); 976 break; 977 case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_VERBATIM: 978 builder.append("tokenizerType: TOKENIZER_TYPE_VERBATIM,\n"); 979 break; 980 case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_RFC822: 981 builder.append("tokenizerType: TOKENIZER_TYPE_RFC822,\n"); 982 break; 983 default: 984 builder.append("tokenizerType: TOKENIZER_TYPE_UNKNOWN,\n"); 985 } 986 987 switch (getJoinableValueType()) { 988 case AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE: 989 builder.append("joinableValueType: JOINABLE_VALUE_TYPE_NONE,\n"); 990 break; 991 case AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_QUALIFIED_ID: 992 builder.append("joinableValueType: JOINABLE_VALUE_TYPE_QUALIFIED_ID,\n"); 993 break; 994 default: 995 builder.append("joinableValueType: JOINABLE_VALUE_TYPE_UNKNOWN,\n"); 996 } 997 } 998 } 999 1000 /** Configuration for a property containing a 64-bit integer. */ 1001 public static final class LongPropertyConfig extends PropertyConfig { 1002 /** 1003 * Encapsulates the configurations on how AppSearch should query/index these 64-bit 1004 * integers. 1005 * 1006 * @hide 1007 */ 1008 @IntDef(value = {INDEXING_TYPE_NONE, INDEXING_TYPE_RANGE}) 1009 @Retention(RetentionPolicy.SOURCE) 1010 public @interface IndexingType {} 1011 1012 /** Content in this property will not be indexed. */ 1013 public static final int INDEXING_TYPE_NONE = 0; 1014 1015 /** 1016 * Content in this property will be indexed and can be fetched via numeric search range 1017 * query. 1018 * 1019 * <p>For example, a property with 1024 should match numeric search range query [0, 2000]. 1020 */ 1021 public static final int INDEXING_TYPE_RANGE = 1; 1022 LongPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1023 LongPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) { 1024 super(propertyConfigParcel); 1025 } 1026 1027 /** Returns how the property is indexed. */ 1028 @LongPropertyConfig.IndexingType getIndexingType()1029 public int getIndexingType() { 1030 PropertyConfigParcel.IntegerIndexingConfigParcel indexingConfigParcel = 1031 mPropertyConfigParcel.getIntegerIndexingConfigParcel(); 1032 if (indexingConfigParcel == null) { 1033 return INDEXING_TYPE_NONE; 1034 } 1035 return indexingConfigParcel.getIndexingType(); 1036 } 1037 1038 /** Builder for {@link LongPropertyConfig}. */ 1039 public static final class Builder { 1040 private final String mPropertyName; 1041 private String mDescription = ""; 1042 @Cardinality private int mCardinality = CARDINALITY_OPTIONAL; 1043 @LongPropertyConfig.IndexingType private int mIndexingType = INDEXING_TYPE_NONE; 1044 1045 /** Creates a new {@link LongPropertyConfig.Builder}. */ Builder(@onNull String propertyName)1046 public Builder(@NonNull String propertyName) { 1047 mPropertyName = Objects.requireNonNull(propertyName); 1048 } 1049 1050 /** 1051 * Sets a natural language description of this property. 1052 * 1053 * <p>For more details about the description field, see {@link 1054 * AppSearchSchema.PropertyConfig#getDescription}. 1055 */ 1056 @CanIgnoreReturnValue 1057 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 1058 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1059 @NonNull setDescription(@onNull String description)1060 public LongPropertyConfig.Builder setDescription(@NonNull String description) { 1061 mDescription = Objects.requireNonNull(description); 1062 return this; 1063 } 1064 1065 /** 1066 * Sets the cardinality of the property (whether it is optional, required or repeated). 1067 * 1068 * <p>If this method is not called, the default cardinality is {@link 1069 * PropertyConfig#CARDINALITY_OPTIONAL}. 1070 */ 1071 @CanIgnoreReturnValue 1072 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1073 @NonNull setCardinality(@ardinality int cardinality)1074 public LongPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 1075 Preconditions.checkArgumentInRange( 1076 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 1077 mCardinality = cardinality; 1078 return this; 1079 } 1080 1081 /** 1082 * Configures how a property should be indexed so that it can be retrieved by queries. 1083 * 1084 * <p>If this method is not called, the default indexing type is {@link 1085 * LongPropertyConfig#INDEXING_TYPE_NONE}, so that it will not be indexed and cannot be 1086 * matched by queries. 1087 */ 1088 @CanIgnoreReturnValue 1089 @NonNull setIndexingType( @ongPropertyConfig.IndexingType int indexingType)1090 public LongPropertyConfig.Builder setIndexingType( 1091 @LongPropertyConfig.IndexingType int indexingType) { 1092 Preconditions.checkArgumentInRange( 1093 indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_RANGE, "indexingType"); 1094 mIndexingType = indexingType; 1095 return this; 1096 } 1097 1098 /** Constructs a new {@link LongPropertyConfig} from the contents of this builder. */ 1099 @NonNull build()1100 public LongPropertyConfig build() { 1101 return new LongPropertyConfig( 1102 PropertyConfigParcel.createForLong( 1103 mPropertyName, mDescription, mCardinality, mIndexingType)); 1104 } 1105 } 1106 1107 /** 1108 * Appends a debug string for the {@link LongPropertyConfig} instance to the given string 1109 * builder. 1110 * 1111 * <p>This appends fields specific to a {@link LongPropertyConfig} instance. 1112 * 1113 * @param builder the builder to append to. 1114 */ appendLongPropertyConfigFields(@onNull IndentingStringBuilder builder)1115 void appendLongPropertyConfigFields(@NonNull IndentingStringBuilder builder) { 1116 switch (getIndexingType()) { 1117 case AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE: 1118 builder.append("indexingType: INDEXING_TYPE_NONE,\n"); 1119 break; 1120 case AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE: 1121 builder.append("indexingType: INDEXING_TYPE_RANGE,\n"); 1122 break; 1123 default: 1124 builder.append("indexingType: INDEXING_TYPE_UNKNOWN,\n"); 1125 } 1126 } 1127 } 1128 1129 /** Configuration for a property containing a double-precision decimal number. */ 1130 public static final class DoublePropertyConfig extends PropertyConfig { DoublePropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1131 DoublePropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) { 1132 super(propertyConfigParcel); 1133 } 1134 1135 /** Builder for {@link DoublePropertyConfig}. */ 1136 public static final class Builder { 1137 private final String mPropertyName; 1138 private String mDescription = ""; 1139 @Cardinality private int mCardinality = CARDINALITY_OPTIONAL; 1140 1141 /** Creates a new {@link DoublePropertyConfig.Builder}. */ Builder(@onNull String propertyName)1142 public Builder(@NonNull String propertyName) { 1143 mPropertyName = Objects.requireNonNull(propertyName); 1144 } 1145 1146 /** 1147 * Sets a natural language description of this property. 1148 * 1149 * <p>For more details about the description field, see {@link 1150 * AppSearchSchema.PropertyConfig#getDescription}. 1151 */ 1152 @CanIgnoreReturnValue 1153 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 1154 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1155 @NonNull setDescription(@onNull String description)1156 public DoublePropertyConfig.Builder setDescription(@NonNull String description) { 1157 mDescription = Objects.requireNonNull(description); 1158 return this; 1159 } 1160 1161 /** 1162 * Sets the cardinality of the property (whether it is optional, required or repeated). 1163 * 1164 * <p>If this method is not called, the default cardinality is {@link 1165 * PropertyConfig#CARDINALITY_OPTIONAL}. 1166 */ 1167 @CanIgnoreReturnValue 1168 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1169 @NonNull setCardinality(@ardinality int cardinality)1170 public DoublePropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 1171 Preconditions.checkArgumentInRange( 1172 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 1173 mCardinality = cardinality; 1174 return this; 1175 } 1176 1177 /** Constructs a new {@link DoublePropertyConfig} from the contents of this builder. */ 1178 @NonNull build()1179 public DoublePropertyConfig build() { 1180 return new DoublePropertyConfig( 1181 PropertyConfigParcel.createForDouble( 1182 mPropertyName, mDescription, mCardinality)); 1183 } 1184 } 1185 } 1186 1187 /** Configuration for a property containing a boolean. */ 1188 public static final class BooleanPropertyConfig extends PropertyConfig { BooleanPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1189 BooleanPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) { 1190 super(propertyConfigParcel); 1191 } 1192 1193 /** Builder for {@link BooleanPropertyConfig}. */ 1194 public static final class Builder { 1195 private final String mPropertyName; 1196 private String mDescription = ""; 1197 @Cardinality private int mCardinality = CARDINALITY_OPTIONAL; 1198 1199 /** Creates a new {@link BooleanPropertyConfig.Builder}. */ Builder(@onNull String propertyName)1200 public Builder(@NonNull String propertyName) { 1201 mPropertyName = Objects.requireNonNull(propertyName); 1202 } 1203 1204 /** 1205 * Sets a natural language description of this property. 1206 * 1207 * <p>For more details about the description field, see {@link 1208 * AppSearchSchema.PropertyConfig#getDescription}. 1209 */ 1210 @CanIgnoreReturnValue 1211 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 1212 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1213 @NonNull setDescription(@onNull String description)1214 public BooleanPropertyConfig.Builder setDescription(@NonNull String description) { 1215 mDescription = Objects.requireNonNull(description); 1216 return this; 1217 } 1218 1219 /** 1220 * Sets the cardinality of the property (whether it is optional, required or repeated). 1221 * 1222 * <p>If this method is not called, the default cardinality is {@link 1223 * PropertyConfig#CARDINALITY_OPTIONAL}. 1224 */ 1225 @CanIgnoreReturnValue 1226 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1227 @NonNull setCardinality(@ardinality int cardinality)1228 public BooleanPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 1229 Preconditions.checkArgumentInRange( 1230 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 1231 mCardinality = cardinality; 1232 return this; 1233 } 1234 1235 /** Constructs a new {@link BooleanPropertyConfig} from the contents of this builder. */ 1236 @NonNull build()1237 public BooleanPropertyConfig build() { 1238 return new BooleanPropertyConfig( 1239 PropertyConfigParcel.createForBoolean( 1240 mPropertyName, mDescription, mCardinality)); 1241 } 1242 } 1243 } 1244 1245 /** Configuration for a property containing a byte array. */ 1246 public static final class BytesPropertyConfig extends PropertyConfig { BytesPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1247 BytesPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) { 1248 super(propertyConfigParcel); 1249 } 1250 1251 /** Builder for {@link BytesPropertyConfig}. */ 1252 public static final class Builder { 1253 private final String mPropertyName; 1254 private String mDescription = ""; 1255 @Cardinality private int mCardinality = CARDINALITY_OPTIONAL; 1256 1257 /** Creates a new {@link BytesPropertyConfig.Builder}. */ Builder(@onNull String propertyName)1258 public Builder(@NonNull String propertyName) { 1259 mPropertyName = Objects.requireNonNull(propertyName); 1260 } 1261 1262 /** 1263 * Sets a natural language description of this property. 1264 * 1265 * <p>For more details about the description field, see {@link 1266 * AppSearchSchema.PropertyConfig#getDescription}. 1267 */ 1268 @CanIgnoreReturnValue 1269 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 1270 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1271 @NonNull setDescription(@onNull String description)1272 public BytesPropertyConfig.Builder setDescription(@NonNull String description) { 1273 mDescription = Objects.requireNonNull(description); 1274 return this; 1275 } 1276 1277 /** 1278 * Sets the cardinality of the property (whether it is optional, required or repeated). 1279 * 1280 * <p>If this method is not called, the default cardinality is {@link 1281 * PropertyConfig#CARDINALITY_OPTIONAL}. 1282 */ 1283 @CanIgnoreReturnValue 1284 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1285 @NonNull setCardinality(@ardinality int cardinality)1286 public BytesPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 1287 Preconditions.checkArgumentInRange( 1288 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 1289 mCardinality = cardinality; 1290 return this; 1291 } 1292 1293 /** Constructs a new {@link BytesPropertyConfig} from the contents of this builder. */ 1294 @NonNull build()1295 public BytesPropertyConfig build() { 1296 return new BytesPropertyConfig( 1297 PropertyConfigParcel.createForBytes( 1298 mPropertyName, mDescription, mCardinality)); 1299 } 1300 } 1301 } 1302 1303 /** Configuration for a property containing another Document. */ 1304 public static final class DocumentPropertyConfig extends PropertyConfig { DocumentPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1305 DocumentPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) { 1306 super(propertyConfigParcel); 1307 } 1308 1309 /** Returns the logical schema-type of the contents of this document property. */ 1310 @NonNull getSchemaType()1311 public String getSchemaType() { 1312 return Objects.requireNonNull(mPropertyConfigParcel.getSchemaType()); 1313 } 1314 1315 /** 1316 * Returns whether properties in the nested document should be indexed according to that 1317 * document's schema. 1318 * 1319 * <p>If false, the nested document's properties are not indexed regardless of its own 1320 * schema. 1321 * 1322 * @see DocumentPropertyConfig.Builder#addIndexableNestedProperties(Collection) for indexing 1323 * a subset of properties from the nested document. 1324 */ shouldIndexNestedProperties()1325 public boolean shouldIndexNestedProperties() { 1326 DocumentIndexingConfigParcel indexingConfigParcel = 1327 mPropertyConfigParcel.getDocumentIndexingConfigParcel(); 1328 if (indexingConfigParcel == null) { 1329 return false; 1330 } 1331 1332 return indexingConfigParcel.shouldIndexNestedProperties(); 1333 } 1334 1335 /** Returns the list of indexable nested properties for the nested document. */ 1336 @FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES) 1337 @NonNull getIndexableNestedProperties()1338 public List<String> getIndexableNestedProperties() { 1339 DocumentIndexingConfigParcel indexingConfigParcel = 1340 mPropertyConfigParcel.getDocumentIndexingConfigParcel(); 1341 if (indexingConfigParcel == null) { 1342 return Collections.emptyList(); 1343 } 1344 1345 List<String> indexableNestedPropertiesList = 1346 indexingConfigParcel.getIndexableNestedPropertiesList(); 1347 if (indexableNestedPropertiesList == null) { 1348 return Collections.emptyList(); 1349 } 1350 1351 return Collections.unmodifiableList(indexableNestedPropertiesList); 1352 } 1353 1354 /** Builder for {@link DocumentPropertyConfig}. */ 1355 public static final class Builder { 1356 private final String mPropertyName; 1357 private final String mSchemaType; 1358 private String mDescription = ""; 1359 @Cardinality private int mCardinality = CARDINALITY_OPTIONAL; 1360 private boolean mShouldIndexNestedProperties = false; 1361 private final Set<String> mIndexableNestedPropertiesList = new ArraySet<>(); 1362 1363 /** 1364 * Creates a new {@link DocumentPropertyConfig.Builder}. 1365 * 1366 * @param propertyName The logical name of the property in the schema, which will be 1367 * used as the key for this property in {@link 1368 * GenericDocument.Builder#setPropertyDocument}. 1369 * @param schemaType The type of documents which will be stored in this property. 1370 * Documents of different types cannot be mixed into a single property. 1371 */ Builder(@onNull String propertyName, @NonNull String schemaType)1372 public Builder(@NonNull String propertyName, @NonNull String schemaType) { 1373 mPropertyName = Objects.requireNonNull(propertyName); 1374 mSchemaType = Objects.requireNonNull(schemaType); 1375 } 1376 1377 /** 1378 * Sets a natural language description of this property. 1379 * 1380 * <p>For more details about the description field, see {@link 1381 * AppSearchSchema.PropertyConfig#getDescription}. 1382 */ 1383 @CanIgnoreReturnValue 1384 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 1385 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1386 @NonNull setDescription(@onNull String description)1387 public DocumentPropertyConfig.Builder setDescription(@NonNull String description) { 1388 mDescription = Objects.requireNonNull(description); 1389 return this; 1390 } 1391 1392 /** 1393 * Sets the cardinality of the property (whether it is optional, required or repeated). 1394 * 1395 * <p>If this method is not called, the default cardinality is {@link 1396 * PropertyConfig#CARDINALITY_OPTIONAL}. 1397 */ 1398 @CanIgnoreReturnValue 1399 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1400 @NonNull setCardinality(@ardinality int cardinality)1401 public DocumentPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 1402 Preconditions.checkArgumentInRange( 1403 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 1404 mCardinality = cardinality; 1405 return this; 1406 } 1407 1408 /** 1409 * Configures whether properties in the nested document should be indexed according to 1410 * that document's schema. 1411 * 1412 * <p>If false, the nested document's properties are not indexed regardless of its own 1413 * schema. 1414 * 1415 * <p>To index a subset of properties from the nested document, set this to false and 1416 * use {@link #addIndexableNestedProperties(Collection)}. 1417 */ 1418 @CanIgnoreReturnValue 1419 @NonNull setShouldIndexNestedProperties( boolean indexNestedProperties)1420 public DocumentPropertyConfig.Builder setShouldIndexNestedProperties( 1421 boolean indexNestedProperties) { 1422 mShouldIndexNestedProperties = indexNestedProperties; 1423 return this; 1424 } 1425 1426 /** 1427 * Adds one or more properties for indexing from the nested document property. 1428 * 1429 * @see #addIndexableNestedProperties(Collection) 1430 */ 1431 @FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES) 1432 @CanIgnoreReturnValue 1433 @NonNull addIndexableNestedProperties( @onNull String... indexableNestedProperties)1434 public DocumentPropertyConfig.Builder addIndexableNestedProperties( 1435 @NonNull String... indexableNestedProperties) { 1436 Objects.requireNonNull(indexableNestedProperties); 1437 return addIndexableNestedProperties(Arrays.asList(indexableNestedProperties)); 1438 } 1439 1440 /** 1441 * Adds one or more property paths for indexing from the nested document property. 1442 * 1443 * @see #addIndexableNestedProperties(Collection) 1444 */ 1445 @FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES) 1446 @CanIgnoreReturnValue 1447 @SuppressLint("MissingGetterMatchingBuilder") 1448 @NonNull addIndexableNestedPropertyPaths( @onNull PropertyPath... indexableNestedPropertyPaths)1449 public DocumentPropertyConfig.Builder addIndexableNestedPropertyPaths( 1450 @NonNull PropertyPath... indexableNestedPropertyPaths) { 1451 Objects.requireNonNull(indexableNestedPropertyPaths); 1452 return addIndexableNestedPropertyPaths(Arrays.asList(indexableNestedPropertyPaths)); 1453 } 1454 1455 /** 1456 * Adds one or more properties for indexing from the nested document. The added property 1457 * will be indexed according to that property's indexing configurations in the 1458 * document's schema definition. All properties in this list will consume a sectionId 1459 * regardless of its actual indexing config -- this includes properties added that do 1460 * not actually exist, as well as properties that are not set as indexable in the nested 1461 * schema type. 1462 * 1463 * <p>Input strings should follow the format of the property path for the nested 1464 * property, with '.' as the path separator. This nested document's property name should 1465 * not be included in the property path. 1466 * 1467 * <p>Ex. Consider an 'Organization' schema type which defines a nested document 1468 * property 'address' (Address schema type), where Address has a nested document 1469 * property 'country' (Country schema type with string 'name' property), and a string 1470 * 'street' property. The 'street' and 'country's name' properties from the 'address' 1471 * document property can be indexed for the 'Organization' schema type by calling: 1472 * 1473 * <pre>{@code 1474 * OrganizationSchema.addProperty( 1475 * new DocumentPropertyConfig.Builder("address", "Address") 1476 * .addIndexableNestedProperties("street", "country.name") 1477 * .build()). 1478 * }</pre> 1479 * 1480 * <p>{@link DocumentPropertyConfig.Builder#setShouldIndexNestedProperties} is required 1481 * to be false if any indexable nested property is added this way for the document 1482 * property. Attempting to build a DocumentPropertyConfig when this is not true throws 1483 * {@link IllegalArgumentException}. 1484 */ 1485 @CanIgnoreReturnValue 1486 @NonNull addIndexableNestedProperties( @onNull Collection<String> indexableNestedProperties)1487 public DocumentPropertyConfig.Builder addIndexableNestedProperties( 1488 @NonNull Collection<String> indexableNestedProperties) { 1489 Objects.requireNonNull(indexableNestedProperties); 1490 mIndexableNestedPropertiesList.addAll(indexableNestedProperties); 1491 return this; 1492 } 1493 1494 /** 1495 * Adds one or more property paths for indexing from the nested document property. 1496 * 1497 * @see #addIndexableNestedProperties(Collection) 1498 */ 1499 @FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES) 1500 @CanIgnoreReturnValue 1501 @SuppressLint("MissingGetterMatchingBuilder") 1502 @NonNull addIndexableNestedPropertyPaths( @onNull Collection<PropertyPath> indexableNestedPropertyPaths)1503 public DocumentPropertyConfig.Builder addIndexableNestedPropertyPaths( 1504 @NonNull Collection<PropertyPath> indexableNestedPropertyPaths) { 1505 Objects.requireNonNull(indexableNestedPropertyPaths); 1506 List<PropertyPath> propertyPathList = new ArrayList<>(indexableNestedPropertyPaths); 1507 for (int i = 0; i < indexableNestedPropertyPaths.size(); i++) { 1508 mIndexableNestedPropertiesList.add(propertyPathList.get(i).toString()); 1509 } 1510 return this; 1511 } 1512 1513 /** 1514 * Constructs a new {@link PropertyConfig} from the contents of this builder. 1515 * 1516 * @throws IllegalArgumentException if the provided PropertyConfig sets {@link 1517 * #shouldIndexNestedProperties()} to true and has one or more properties defined 1518 * using {@link #addIndexableNestedProperties(Collection)}. 1519 */ 1520 @NonNull build()1521 public DocumentPropertyConfig build() { 1522 if (mShouldIndexNestedProperties && !mIndexableNestedPropertiesList.isEmpty()) { 1523 throw new IllegalArgumentException( 1524 "DocumentIndexingConfig#shouldIndexNestedProperties is required " 1525 + "to be false when one or more indexableNestedProperties are " 1526 + "provided."); 1527 } 1528 return new DocumentPropertyConfig( 1529 PropertyConfigParcel.createForDocument( 1530 mPropertyName, 1531 mDescription, 1532 mCardinality, 1533 mSchemaType, 1534 new DocumentIndexingConfigParcel( 1535 mShouldIndexNestedProperties, 1536 new ArrayList<>(mIndexableNestedPropertiesList)))); 1537 } 1538 } 1539 1540 /** 1541 * Appends a debug string for the {@link DocumentPropertyConfig} instance to the given 1542 * string builder. 1543 * 1544 * <p>This appends fields specific to a {@link DocumentPropertyConfig} instance. 1545 * 1546 * @param builder the builder to append to. 1547 */ appendDocumentPropertyConfigFields(@onNull IndentingStringBuilder builder)1548 void appendDocumentPropertyConfigFields(@NonNull IndentingStringBuilder builder) { 1549 builder.append("shouldIndexNestedProperties: ") 1550 .append(shouldIndexNestedProperties()) 1551 .append(",\n"); 1552 1553 builder.append("indexableNestedProperties: ") 1554 .append(getIndexableNestedProperties()) 1555 .append(",\n"); 1556 1557 builder.append("schemaType: \"").append(getSchemaType()).append("\",\n"); 1558 } 1559 } 1560 1561 /** Configuration for a property of type {@link EmbeddingVector} in a Document. */ 1562 @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG) 1563 public static final class EmbeddingPropertyConfig extends PropertyConfig { 1564 /** 1565 * Encapsulates the configurations on how AppSearch should query/index these embedding 1566 * vectors. 1567 * 1568 * @hide 1569 */ 1570 @IntDef(value = {INDEXING_TYPE_NONE, INDEXING_TYPE_SIMILARITY}) 1571 @Retention(RetentionPolicy.SOURCE) 1572 public @interface IndexingType {} 1573 1574 /** Content in this property will not be indexed. */ 1575 public static final int INDEXING_TYPE_NONE = 0; 1576 1577 /** 1578 * Embedding vectors in this property will be indexed. 1579 * 1580 * <p>The index offers 100% accuracy, but has linear time complexity based on the number of 1581 * embedding vectors within the index. 1582 */ 1583 public static final int INDEXING_TYPE_SIMILARITY = 1; 1584 EmbeddingPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1585 EmbeddingPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) { 1586 super(propertyConfigParcel); 1587 } 1588 1589 /** Returns how the property is indexed. */ 1590 @EmbeddingPropertyConfig.IndexingType getIndexingType()1591 public int getIndexingType() { 1592 PropertyConfigParcel.EmbeddingIndexingConfigParcel indexingConfigParcel = 1593 mPropertyConfigParcel.getEmbeddingIndexingConfigParcel(); 1594 if (indexingConfigParcel == null) { 1595 return INDEXING_TYPE_NONE; 1596 } 1597 return indexingConfigParcel.getIndexingType(); 1598 } 1599 1600 /** Builder for {@link EmbeddingPropertyConfig}. */ 1601 @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG) 1602 public static final class Builder { 1603 private final String mPropertyName; 1604 private String mDescription = ""; 1605 @Cardinality private int mCardinality = CARDINALITY_OPTIONAL; 1606 @EmbeddingPropertyConfig.IndexingType private int mIndexingType = INDEXING_TYPE_NONE; 1607 1608 /** Creates a new {@link EmbeddingPropertyConfig.Builder}. */ Builder(@onNull String propertyName)1609 public Builder(@NonNull String propertyName) { 1610 mPropertyName = Objects.requireNonNull(propertyName); 1611 } 1612 1613 /** 1614 * Sets a natural language description of this property. 1615 * 1616 * <p>For more details about the description field, see {@link 1617 * AppSearchSchema.PropertyConfig#getDescription}. 1618 */ 1619 @CanIgnoreReturnValue 1620 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 1621 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1622 @NonNull setDescription(@onNull String description)1623 public EmbeddingPropertyConfig.Builder setDescription(@NonNull String description) { 1624 mDescription = Objects.requireNonNull(description); 1625 return this; 1626 } 1627 1628 /** 1629 * Sets the cardinality of the property (whether it is optional, required or repeated). 1630 * 1631 * <p>If this method is not called, the default cardinality is {@link 1632 * PropertyConfig#CARDINALITY_OPTIONAL}. 1633 */ 1634 @CanIgnoreReturnValue 1635 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 1636 @NonNull setCardinality(@ardinality int cardinality)1637 public EmbeddingPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 1638 Preconditions.checkArgumentInRange( 1639 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 1640 mCardinality = cardinality; 1641 return this; 1642 } 1643 1644 /** 1645 * Configures how a property should be indexed so that it can be retrieved by queries. 1646 * 1647 * <p>If this method is not called, the default indexing type is {@link 1648 * EmbeddingPropertyConfig#INDEXING_TYPE_NONE}, so that it will not be indexed and 1649 * cannot be matched by queries. 1650 */ 1651 @CanIgnoreReturnValue 1652 @NonNull setIndexingType( @mbeddingPropertyConfig.IndexingType int indexingType)1653 public EmbeddingPropertyConfig.Builder setIndexingType( 1654 @EmbeddingPropertyConfig.IndexingType int indexingType) { 1655 Preconditions.checkArgumentInRange( 1656 indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_SIMILARITY, "indexingType"); 1657 mIndexingType = indexingType; 1658 return this; 1659 } 1660 1661 /** 1662 * Constructs a new {@link EmbeddingPropertyConfig} from the contents of this builder. 1663 */ 1664 @NonNull build()1665 public EmbeddingPropertyConfig build() { 1666 return new EmbeddingPropertyConfig( 1667 PropertyConfigParcel.createForEmbedding( 1668 mPropertyName, mDescription, mCardinality, mIndexingType)); 1669 } 1670 } 1671 } 1672 } 1673