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 &lt;: T2 and T2 &lt;: T3 are
265          * known, then T1 &lt;: T3 will be inferred automatically, where &lt;: 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 &lt;: 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 &lt;: 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 &lt; OPTIONAL &lt; 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