1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.appsearch.app;
18 
19 import android.annotation.SuppressLint;
20 import android.os.Bundle;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 
24 import androidx.annotation.IntDef;
25 import androidx.annotation.NonNull;
26 import androidx.annotation.Nullable;
27 import androidx.annotation.OptIn;
28 import androidx.annotation.RequiresFeature;
29 import androidx.annotation.RestrictTo;
30 import androidx.appsearch.annotation.CanIgnoreReturnValue;
31 import androidx.appsearch.exceptions.AppSearchException;
32 import androidx.appsearch.exceptions.IllegalSchemaException;
33 import androidx.appsearch.flags.FlaggedApi;
34 import androidx.appsearch.flags.Flags;
35 import androidx.appsearch.safeparcel.AbstractSafeParcelable;
36 import androidx.appsearch.safeparcel.PropertyConfigParcel;
37 import androidx.appsearch.safeparcel.PropertyConfigParcel.DocumentIndexingConfigParcel;
38 import androidx.appsearch.safeparcel.PropertyConfigParcel.JoinableConfigParcel;
39 import androidx.appsearch.safeparcel.PropertyConfigParcel.StringIndexingConfigParcel;
40 import androidx.appsearch.safeparcel.SafeParcelable;
41 import androidx.appsearch.safeparcel.stub.StubCreators.AppSearchSchemaCreator;
42 import androidx.appsearch.util.IndentingStringBuilder;
43 import androidx.collection.ArraySet;
44 import androidx.core.util.ObjectsCompat;
45 import androidx.core.util.Preconditions;
46 
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.LinkedHashSet;
54 import java.util.List;
55 import java.util.Set;
56 
57 /**
58  * The AppSearch Schema for a particular type of document.
59  *
60  * <p>For example, an e-mail message or a music recording could be a schema type.
61  *
62  * <p>The schema consists of type information, properties, and config (like tokenization type).
63  *
64  * @see AppSearchSession#setSchemaAsync
65  */
66 @SafeParcelable.Class(creator = "AppSearchSchemaCreator")
67 // TODO(b/384721898): Switch to JSpecify annotations
68 @SuppressWarnings({"HiddenSuperclass", "JSpecifyNullness"})
69 public final class AppSearchSchema extends AbstractSafeParcelable {
70     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
71     @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2)
72     public static final @NonNull Parcelable.Creator<AppSearchSchema> CREATOR =
73             new AppSearchSchemaCreator();
74 
75     @Field(id = 1, getter = "getSchemaType")
76     private final String mSchemaType;
77 
78     @Field(id = 2)
79     final List<PropertyConfigParcel> mPropertyConfigParcels;
80 
81     @Field(id = 3, getter = "getParentTypes")
82     private final List<String> mParentTypes;
83 
84     @Field(id = 4, getter = "getDescription")
85     private final String mDescription;
86 
87     @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)88     AppSearchSchema(
89             @Param(id = 1) @NonNull String schemaType,
90             @Param(id = 2) @NonNull List<PropertyConfigParcel> propertyConfigParcels,
91             @Param(id = 3) @NonNull List<String> parentTypes,
92             @Param(id = 4) @NonNull String description) {
93         mSchemaType = Preconditions.checkNotNull(schemaType);
94         mPropertyConfigParcels = Preconditions.checkNotNull(propertyConfigParcels);
95         mParentTypes = Preconditions.checkNotNull(parentTypes);
96         mDescription = Preconditions.checkNotNull(description);
97     }
98 
99     @Override
toString()100     public @NonNull String toString() {
101         IndentingStringBuilder stringBuilder = new IndentingStringBuilder();
102         appendAppSearchSchemaString(stringBuilder);
103         return stringBuilder.toString();
104     }
105 
106     /**
107      * Appends a debugging string for the {@link AppSearchSchema} instance to the given string
108      * builder.
109      *
110      * @param builder the builder to append to.
111      */
112     @OptIn(markerClass = ExperimentalAppSearchApi.class)
appendAppSearchSchemaString(@onNull IndentingStringBuilder builder)113     private void appendAppSearchSchemaString(@NonNull IndentingStringBuilder builder) {
114         Preconditions.checkNotNull(builder);
115 
116         builder.append("{\n");
117         builder.increaseIndentLevel();
118         builder.append("schemaType: \"").append(getSchemaType()).append("\",\n");
119         builder.append("description: \"").append(getDescription()).append("\",\n");
120         builder.append("properties: [\n");
121 
122         AppSearchSchema.PropertyConfig[] sortedProperties = getProperties()
123                 .toArray(new AppSearchSchema.PropertyConfig[0]);
124         Arrays.sort(sortedProperties, (o1, o2) -> o1.getName().compareTo(o2.getName()));
125 
126         for (int i = 0; i < sortedProperties.length; i++) {
127             AppSearchSchema.PropertyConfig propertyConfig = sortedProperties[i];
128             builder.increaseIndentLevel();
129             propertyConfig.appendPropertyConfigString(builder);
130             if (i != sortedProperties.length - 1) {
131                 builder.append(",\n");
132             }
133             builder.decreaseIndentLevel();
134         }
135 
136         builder.append("\n");
137         builder.append("]\n");
138         builder.decreaseIndentLevel();
139         builder.append("}");
140     }
141 
142     /** Returns the name of this schema type, such as Email. */
getSchemaType()143     public @NonNull String getSchemaType() {
144         return mSchemaType;
145     }
146 
147     /**
148      * Returns a natural language description of this schema type.
149      *
150      * <p>Ex. The description for an Email type could be "A type of electronic message".
151      *
152      * <p>This information is purely to help apps consuming this type to understand its semantic
153      * meaning. This field has no effect in AppSearch - it is just stored with the AppSearchSchema.
154      * If {@link Builder#setDescription} is uncalled, then this method will return an empty string.
155      */
156     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
157     @ExperimentalAppSearchApi
getDescription()158     public @NonNull String getDescription() {
159         return mDescription;
160     }
161 
162     /**
163      * Returns the list of {@link PropertyConfig}s that are part of this schema.
164      *
165      * <p>This method creates a new list when called.
166      */
167     @SuppressWarnings({"MixedMutabilityReturnType"})
getProperties()168     public @NonNull List<PropertyConfig> getProperties() {
169         if (mPropertyConfigParcels.isEmpty()) {
170             return Collections.emptyList();
171         }
172         List<PropertyConfig> ret = new ArrayList<>(mPropertyConfigParcels.size());
173         for (int i = 0; i < mPropertyConfigParcels.size(); i++) {
174             ret.add(PropertyConfig.fromParcel(mPropertyConfigParcels.get(i)));
175         }
176         return ret;
177     }
178 
179     /**
180      * Returns the list of parent types of this schema for polymorphism.
181      */
182     @FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES)
getParentTypes()183     public @NonNull List<String> getParentTypes() {
184         return Collections.unmodifiableList(mParentTypes);
185     }
186 
187     @Override
188     @OptIn(markerClass = ExperimentalAppSearchApi.class)
equals(@ullable Object other)189     public boolean equals(@Nullable Object other) {
190         if (this == other) {
191             return true;
192         }
193         if (!(other instanceof AppSearchSchema)) {
194             return false;
195         }
196         AppSearchSchema otherSchema = (AppSearchSchema) other;
197         if (!getSchemaType().equals(otherSchema.getSchemaType())) {
198             return false;
199         }
200         if (!getDescription().equals(otherSchema.getDescription())) {
201             return false;
202         }
203         if (!getParentTypes().equals(otherSchema.getParentTypes())) {
204             return false;
205         }
206         return getProperties().equals(otherSchema.getProperties());
207     }
208 
209     @Override
210     @OptIn(markerClass = ExperimentalAppSearchApi.class)
hashCode()211     public int hashCode() {
212         return ObjectsCompat.hash(
213             getSchemaType(),
214             getProperties(),
215             getParentTypes(),
216             getDescription());
217     }
218 
219     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
220     @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2)
221     @Override
writeToParcel(@onNull Parcel dest, int flags)222     public void writeToParcel(@NonNull Parcel dest, int flags) {
223         AppSearchSchemaCreator.writeToParcel(this, dest, flags);
224     }
225 
226     /** Builder for {@link AppSearchSchema objects}. */
227     public static final class Builder {
228         private String mSchemaType;
229         private String mDescription = "";
230         private ArrayList<PropertyConfigParcel> mPropertyConfigParcels = new ArrayList<>();
231         private LinkedHashSet<String> mParentTypes = new LinkedHashSet<>();
232         private final Set<String> mPropertyNames = new ArraySet<>();
233         private boolean mBuilt = false;
234 
235         /** Creates a new {@link AppSearchSchema.Builder}. */
Builder(@onNull String schemaType)236         public Builder(@NonNull String schemaType) {
237             mSchemaType = Preconditions.checkNotNull(schemaType);
238         }
239 
240         /**
241          * Creates a new {@link AppSearchSchema.Builder} from the given {@link AppSearchSchema}.
242          */
243         @ExperimentalAppSearchApi
244         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
245         @OptIn(markerClass = ExperimentalAppSearchApi.class)
Builder(@onNull AppSearchSchema schema)246         public Builder(@NonNull AppSearchSchema schema) {
247             mSchemaType = schema.getSchemaType();
248             mDescription = schema.getDescription();
249             mPropertyConfigParcels.addAll(schema.mPropertyConfigParcels);
250             mParentTypes.addAll(schema.mParentTypes);
251             for (int i = 0; i < mPropertyConfigParcels.size(); i++) {
252                 mPropertyNames.add(mPropertyConfigParcels.get(i).getName());
253             }
254         }
255 
256         /** Sets the schema type name. */
257         @ExperimentalAppSearchApi
258         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
259         @CanIgnoreReturnValue
setSchemaType(@onNull String schemaType)260         public @NonNull AppSearchSchema.Builder setSchemaType(@NonNull String schemaType) {
261             Preconditions.checkNotNull(schemaType);
262             resetIfBuilt();
263             mSchemaType = schemaType;
264             return this;
265         }
266 
267         /**
268          * Sets a natural language description of this schema type.
269          *
270          * <p> For more details about the description field, see {@link
271          * AppSearchSchema#getDescription}.
272          */
273         @RequiresFeature(
274                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
275                 name = Features.SCHEMA_SET_DESCRIPTION)
276         @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
277         @ExperimentalAppSearchApi
278         @CanIgnoreReturnValue
setDescription(@onNull String description)279         public @NonNull AppSearchSchema.Builder setDescription(@NonNull String description) {
280             Preconditions.checkNotNull(description);
281             resetIfBuilt();
282             mDescription = description;
283             return this;
284         }
285 
286         /** Adds a property to the schema type. */
287         @CanIgnoreReturnValue
addProperty( @onNull PropertyConfig propertyConfig)288         public @NonNull AppSearchSchema.Builder addProperty(
289                 @NonNull PropertyConfig propertyConfig) {
290             Preconditions.checkNotNull(propertyConfig);
291             resetIfBuilt();
292             String name = propertyConfig.getName();
293             if (!mPropertyNames.add(name)) {
294                 throw new IllegalSchemaException("Property defined more than once: " + name);
295             }
296             mPropertyConfigParcels.add(propertyConfig.mPropertyConfigParcel);
297             return this;
298         }
299 
300         /**
301          * Clears all properties added through {@link #addProperty(PropertyConfig)} from the schema
302          * type.
303          */
304         @ExperimentalAppSearchApi
305         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
306         @CanIgnoreReturnValue
clearProperties()307         public @NonNull AppSearchSchema.Builder clearProperties() {
308             resetIfBuilt();
309             mPropertyConfigParcels.clear();
310             mPropertyNames.clear();
311             return this;
312         }
313 
314         /**
315          * Adds a parent type to the schema type for polymorphism, so that the schema type will be
316          * considered as a subtype of {@code parentSchemaType}.
317          *
318          * <p>Subtype relations are automatically considered transitive, so callers are only
319          * required to provide direct parents. Specifically, if T1 &lt;: T2 and T2 &lt;: T3 are
320          * known, then T1 &lt;: T3 will be inferred automatically, where &lt;: is the subtype
321          * symbol.
322          *
323          * <p>Polymorphism is currently supported in the following ways:
324          * <ul>
325          *     <li>Search filters on a parent type will automatically be extended to the child
326          *     types as well. For example, if Artist &lt;: Person, then a search with a filter on
327          *     type Person (by calling {@link SearchSpec.Builder#addFilterSchemas}) will also
328          *     include documents of type Artist in the search result.
329          *     <li>In the projection API, the property paths to project specified for a
330          *     parent type will automatically be extended to the child types as well. If both a
331          *     parent type and one of its child type are specified in the projection API, the
332          *     parent type's paths will be merged into the child's. For more details on
333          *     projection, see {@link SearchSpec.Builder#addProjection}.
334          *     <li>A document property defined as type U is allowed to be set with a document of
335          *     type T, as long as T &lt;: U, but note that index will only be based on the defined
336          *     type, which is U. For example, consider a document of type "Company" with a
337          *     repeated "employees" field of type "Person". We can add employees of either
338          *     type "Person" or type "Artist" or both to this property, as long as "Artist" is a
339          *     subtype of "Person". However, the index of the "employees" property will be based
340          *     on what's defined in "Person", even for an added document of type "Artist".
341          * </ul>
342          *
343          * <p>Subtypes must meet the following requirements. A violation of the requirements will
344          * cause {@link AppSearchSession#setSchemaAsync} to throw an {@link AppSearchException}
345          * with the result code of {@link AppSearchResult#RESULT_INVALID_ARGUMENT}. Consider a
346          * type Artist and a type Person, and Artist claims to be a subtype of Person, then:
347          * <ul>
348          *     <li>Every property in Person must have a corresponding property in Artist with the
349          *     same name.
350          *     <li>Every non-document property in Person must have the same type as the type of
351          *     the corresponding property in Artist. For example, if "age" is an integer property
352          *     in Person, then "age" must also be an integer property in Artist, instead of a
353          *     string.
354          *     <li>The schema type of every document property in Artist must be a subtype of the
355          *     schema type of the corresponding document property in Person, if such a property
356          *     exists in Person. For example, if "awards" is a document property of type Award in
357          *     Person, then the type of the "awards" property in Artist must be a subtype of
358          *     Award, say ArtAward. Note that every type is a subtype of itself.
359          *     <li>Every property in Artist must have a cardinality stricter than or equal to the
360          *     cardinality of the corresponding property in Person, if such a property exists in
361          *     Person. For example, if "awards" is a property in Person of cardinality OPTIONAL,
362          *     then the cardinality of the "awards" property in Artist can only be REQUIRED or
363          *     OPTIONAL. Rule: REQUIRED &lt; OPTIONAL &lt; REPEATED.
364          *     <li>There are no other enforcements on the corresponding properties in Artist,
365          *     such as index type, tokenizer type, etc. These settings can be safely overridden.
366          * </ul>
367          *
368          * <p>A type can be defined to have multiple parents, but it must be compatible with each
369          * of its parents based on the above rules. For example, if LocalBusiness is defined as a
370          * subtype of both Place and Organization, then the compatibility of LocalBusiness with
371          * Place and the compatibility of LocalBusiness with Organization will both be checked.
372          */
373         @CanIgnoreReturnValue
374         @RequiresFeature(
375                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
376                 name = Features.SCHEMA_ADD_PARENT_TYPE)
addParentType(@onNull String parentSchemaType)377         public @NonNull AppSearchSchema.Builder addParentType(@NonNull String parentSchemaType) {
378             Preconditions.checkNotNull(parentSchemaType);
379             resetIfBuilt();
380             mParentTypes.add(parentSchemaType);
381             return this;
382         }
383 
384         /**
385          * Clears all parent types added through {@link #addParentType(String)} from the schema
386          * type.
387          */
388         @ExperimentalAppSearchApi
389         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
390         @CanIgnoreReturnValue
clearParentTypes()391         public @NonNull AppSearchSchema.Builder clearParentTypes() {
392             resetIfBuilt();
393             mParentTypes.clear();
394             return this;
395         }
396 
397         /** Constructs a new {@link AppSearchSchema} from the contents of this builder. */
build()398         public @NonNull AppSearchSchema build() {
399             mBuilt = true;
400             return new AppSearchSchema(mSchemaType,
401                     mPropertyConfigParcels,
402                     new ArrayList<>(mParentTypes),
403                     mDescription);
404         }
405 
resetIfBuilt()406         private void resetIfBuilt() {
407             if (mBuilt) {
408                 mPropertyConfigParcels = new ArrayList<>(mPropertyConfigParcels);
409                 mParentTypes = new LinkedHashSet<>(mParentTypes);
410                 mBuilt = false;
411             }
412         }
413     }
414 
415     /**
416      * Common configuration for a single property (field) in a Document.
417      *
418      * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be
419      * a property.
420      */
421     public abstract static class PropertyConfig {
422         /**
423          * Physical data-types of the contents of the property.
424          *
425          * <p>NOTE: The integer values of these constants must match the proto enum constants in
426          * com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
427          *
428          * @exportToFramework:hide
429          */
430         @RestrictTo(RestrictTo.Scope.LIBRARY)
431         @IntDef(value = {
432                 DATA_TYPE_STRING,
433                 DATA_TYPE_LONG,
434                 DATA_TYPE_DOUBLE,
435                 DATA_TYPE_BOOLEAN,
436                 DATA_TYPE_BYTES,
437                 DATA_TYPE_DOCUMENT,
438                 DATA_TYPE_EMBEDDING,
439                 DATA_TYPE_BLOB_HANDLE,
440         })
441         @Retention(RetentionPolicy.SOURCE)
442         public @interface DataType {
443         }
444 
445         /**
446          * Constant value for String data type.
447          *
448          * @exportToFramework:hide
449          */
450         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
451         public static final int DATA_TYPE_STRING = 1;
452 
453         /**
454          * Constant value for Long data type.
455          *
456          * @exportToFramework:hide
457          */
458         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
459         public static final int DATA_TYPE_LONG = 2;
460 
461         /**
462          * Constant value for Double data type.
463          *
464          * @exportToFramework:hide
465          */
466         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
467         public static final int DATA_TYPE_DOUBLE = 3;
468 
469         /**
470          * Constant value for Boolean data type.
471          *
472          * @exportToFramework:hide
473          */
474         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
475         public static final int DATA_TYPE_BOOLEAN = 4;
476 
477         /**
478          * Unstructured BLOB.
479          *
480          * @exportToFramework:hide
481          */
482         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
483         public static final int DATA_TYPE_BYTES = 5;
484 
485         /**
486          * Indicates that the property is itself a {@link GenericDocument}, making it part of a
487          * hierarchical schema. Any property using this DataType MUST have a valid
488          * {@link PropertyConfig#getSchemaType}.
489          *
490          * @exportToFramework:hide
491          */
492         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
493         public static final int DATA_TYPE_DOCUMENT = 6;
494 
495         /**
496          * Indicates that the property is an {@link EmbeddingVector}.
497          *
498          * @exportToFramework:hide
499          */
500         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
501         public static final int DATA_TYPE_EMBEDDING = 7;
502 
503         /**
504          * Indicates that the property is an {@link AppSearchBlobHandle}.
505          *
506          * @exportToFramework:hide
507          */
508         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
509         public static final int DATA_TYPE_BLOB_HANDLE = 8;
510 
511         /**
512          * The cardinality of the property (whether it is required, optional or repeated).
513          *
514          * <p>NOTE: The integer values of these constants must match the proto enum constants in
515          * com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
516          *
517          * @exportToFramework:hide
518          */
519         @RestrictTo(RestrictTo.Scope.LIBRARY)
520         @IntDef(value = {
521                 CARDINALITY_REPEATED,
522                 CARDINALITY_OPTIONAL,
523                 CARDINALITY_REQUIRED,
524         })
525         @Retention(RetentionPolicy.SOURCE)
526         public @interface Cardinality {
527         }
528 
529         /** Any number of items (including zero) [0...*]. */
530         public static final int CARDINALITY_REPEATED = 1;
531 
532         /** Zero or one value [0,1]. */
533         public static final int CARDINALITY_OPTIONAL = 2;
534 
535         /** Exactly one value [1]. */
536         public static final int CARDINALITY_REQUIRED = 3;
537 
538         final PropertyConfigParcel mPropertyConfigParcel;
539 
PropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)540         PropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
541             mPropertyConfigParcel = Preconditions.checkNotNull(propertyConfigParcel);
542         }
543 
544         @Override
toString()545         public @NonNull String toString() {
546             IndentingStringBuilder stringBuilder = new IndentingStringBuilder();
547             appendPropertyConfigString(stringBuilder);
548             return stringBuilder.toString();
549         }
550 
551         /**
552          * Appends a debug string for the {@link AppSearchSchema.PropertyConfig} instance to the
553          * given string builder.
554          *
555          * @param builder the builder to append to.
556          */
557         @OptIn(markerClass = ExperimentalAppSearchApi.class)
appendPropertyConfigString(@onNull IndentingStringBuilder builder)558         void appendPropertyConfigString(@NonNull IndentingStringBuilder builder) {
559             Preconditions.checkNotNull(builder);
560 
561             builder.append("{\n");
562             builder.increaseIndentLevel();
563             builder.append("name: \"").append(getName()).append("\",\n");
564             builder.append("description: \"").append(getDescription()).append("\",\n");
565 
566             if (this instanceof AppSearchSchema.StringPropertyConfig) {
567                 ((StringPropertyConfig) this)
568                         .appendStringPropertyConfigFields(builder);
569             } else if (this instanceof AppSearchSchema.DocumentPropertyConfig) {
570                 ((DocumentPropertyConfig) this)
571                         .appendDocumentPropertyConfigFields(builder);
572             } else if (this instanceof AppSearchSchema.LongPropertyConfig) {
573                 ((LongPropertyConfig) this)
574                         .appendLongPropertyConfigFields(builder);
575             }
576 
577             switch (getCardinality()) {
578                 case AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED:
579                     builder.append("cardinality: CARDINALITY_REPEATED,\n");
580                     break;
581                 case AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL:
582                     builder.append("cardinality: CARDINALITY_OPTIONAL,\n");
583                     break;
584                 case AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED:
585                     builder.append("cardinality: CARDINALITY_REQUIRED,\n");
586                     break;
587                 default:
588                     builder.append("cardinality: CARDINALITY_UNKNOWN,\n");
589             }
590 
591             switch (getDataType()) {
592                 case AppSearchSchema.PropertyConfig.DATA_TYPE_STRING:
593                     builder.append("dataType: DATA_TYPE_STRING,\n");
594                     break;
595                 case AppSearchSchema.PropertyConfig.DATA_TYPE_LONG:
596                     builder.append("dataType: DATA_TYPE_LONG,\n");
597                     break;
598                 case AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE:
599                     builder.append("dataType: DATA_TYPE_DOUBLE,\n");
600                     break;
601                 case AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN:
602                     builder.append("dataType: DATA_TYPE_BOOLEAN,\n");
603                     break;
604                 case AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES:
605                     builder.append("dataType: DATA_TYPE_BYTES,\n");
606                     break;
607                 case AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT:
608                     builder.append("dataType: DATA_TYPE_DOCUMENT,\n");
609                     break;
610                 case PropertyConfig.DATA_TYPE_EMBEDDING:
611                     builder.append("dataType: DATA_TYPE_EMBEDDING,\n");
612                     break;
613                 case PropertyConfig.DATA_TYPE_BLOB_HANDLE:
614                     builder.append("dataType: DATA_TYPE_BLOB_HANDLE,\n");
615                     break;
616                 default:
617                     builder.append("dataType: DATA_TYPE_UNKNOWN,\n");
618             }
619             builder.decreaseIndentLevel();
620             builder.append("}");
621         }
622 
623         /** Returns the name of this property. */
getName()624         public @NonNull String getName() {
625             return mPropertyConfigParcel.getName();
626         }
627 
628         /**
629          * Returns a natural language description of this property.
630          *
631          * <p>Ex. The description for the "homeAddress" property of a "Person" type could be "the
632          * address at which this person lives".
633          *
634          * <p>This information is purely to help apps consuming this type the semantic meaning of
635          * its properties. This field has no effect in AppSearch - it is just stored with the
636          * AppSearchSchema. If the description is not set, then this method will return an empty
637          * string.
638          */
639         @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
640         @ExperimentalAppSearchApi
getDescription()641         public @NonNull String getDescription() {
642             return mPropertyConfigParcel.getDescription();
643         }
644 
645         /**
646          * Returns the type of data the property contains (such as string, int, bytes, etc).
647          *
648          * @exportToFramework:hide
649          */
650         @RestrictTo(RestrictTo.Scope.LIBRARY)
651         @DataType
getDataType()652         public int getDataType() {
653             return mPropertyConfigParcel.getDataType();
654         }
655 
656         /**
657          * Returns the cardinality of the property (whether it is optional, required or repeated).
658          */
659         @Cardinality
getCardinality()660         public int getCardinality() {
661             return mPropertyConfigParcel.getCardinality();
662         }
663 
664         @Override
equals(@ullable Object other)665         public boolean equals(@Nullable Object other) {
666             if (this == other) {
667                 return true;
668             }
669             if (!(other instanceof PropertyConfig)) {
670                 return false;
671             }
672             PropertyConfig otherProperty = (PropertyConfig) other;
673             return ObjectsCompat.equals(mPropertyConfigParcel, otherProperty.mPropertyConfigParcel);
674         }
675 
676         @Override
hashCode()677         public int hashCode() {
678             return mPropertyConfigParcel.hashCode();
679         }
680 
681         /**
682          * Converts a {@link Bundle} into a {@link PropertyConfig} depending on its internal data
683          * type.
684          *
685          * <p>The bundle is not cloned.
686          *
687          * @throws IllegalArgumentException if the bundle does no contain a recognized
688          *                                  value in its {@code DATA_TYPE_FIELD}.
689          * @exportToFramework:hide
690          */
691         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
692         @OptIn(markerClass = ExperimentalAppSearchApi.class)
fromParcel( @onNull PropertyConfigParcel propertyConfigParcel)693         public static @NonNull PropertyConfig fromParcel(
694                 @NonNull PropertyConfigParcel propertyConfigParcel) {
695             Preconditions.checkNotNull(propertyConfigParcel);
696             switch (propertyConfigParcel.getDataType()) {
697                 case PropertyConfig.DATA_TYPE_STRING:
698                     return new StringPropertyConfig(propertyConfigParcel);
699                 case PropertyConfig.DATA_TYPE_LONG:
700                     return new LongPropertyConfig(propertyConfigParcel);
701                 case PropertyConfig.DATA_TYPE_DOUBLE:
702                     return new DoublePropertyConfig(propertyConfigParcel);
703                 case PropertyConfig.DATA_TYPE_BOOLEAN:
704                     return new BooleanPropertyConfig(propertyConfigParcel);
705                 case PropertyConfig.DATA_TYPE_BYTES:
706                     return new BytesPropertyConfig(propertyConfigParcel);
707                 case PropertyConfig.DATA_TYPE_DOCUMENT:
708                     return new DocumentPropertyConfig(propertyConfigParcel);
709                 case PropertyConfig.DATA_TYPE_EMBEDDING:
710                     return new EmbeddingPropertyConfig(propertyConfigParcel);
711                 case PropertyConfig.DATA_TYPE_BLOB_HANDLE:
712                     return new BlobHandlePropertyConfig(propertyConfigParcel);
713                 default:
714                     throw new IllegalArgumentException(
715                             "Unsupported property bundle of type "
716                                     + propertyConfigParcel.getDataType()
717                                     + "; contents: " + propertyConfigParcel);
718             }
719         }
720     }
721 
722     /** Configuration for a property of type String in a Document. */
723     public static final class StringPropertyConfig extends PropertyConfig {
724         /**
725          * Encapsulates the configurations on how AppSearch should query/index these terms.
726          * @exportToFramework:hide
727          */
728         @RestrictTo(RestrictTo.Scope.LIBRARY)
729         @IntDef(value = {
730                 INDEXING_TYPE_NONE,
731                 INDEXING_TYPE_EXACT_TERMS,
732                 INDEXING_TYPE_PREFIXES,
733         })
734         @Retention(RetentionPolicy.SOURCE)
735         public @interface IndexingType {
736         }
737 
738         /** Content in this property will not be tokenized or indexed. */
739         public static final int INDEXING_TYPE_NONE = 0;
740 
741         /**
742          * Content in this property should only be returned for queries matching the exact tokens
743          * appearing in this property.
744          *
745          * <p>For example, a property with "fool" should NOT match a query for "foo".
746          */
747         public static final int INDEXING_TYPE_EXACT_TERMS = 1;
748 
749         /**
750          * Content in this property should be returned for queries that are either exact matches or
751          * query matches of the tokens appearing in this property.
752          *
753          * <p>For example, a property with "fool" <b>should</b> match a query for "foo".
754          */
755         public static final int INDEXING_TYPE_PREFIXES = 2;
756 
757         /**
758          * Configures how tokens should be extracted from this property.
759          *
760          * <p>NOTE: The integer values of these constants must match the proto enum constants in
761          * com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
762          *
763          * @exportToFramework:hide
764          */
765         @RestrictTo(RestrictTo.Scope.LIBRARY)
766         @IntDef(value = {
767                 TOKENIZER_TYPE_NONE,
768                 TOKENIZER_TYPE_PLAIN,
769                 TOKENIZER_TYPE_VERBATIM,
770                 TOKENIZER_TYPE_RFC822
771         })
772         @Retention(RetentionPolicy.SOURCE)
773         public @interface TokenizerType {
774         }
775 
776         /**
777          * This value indicates that no tokens should be extracted from this property.
778          *
779          * <p>It is only valid for tokenizer_type to be 'NONE' if {@link #getIndexingType} is
780          * {@link #INDEXING_TYPE_NONE}.
781          */
782         public static final int TOKENIZER_TYPE_NONE = 0;
783 
784         /**
785          * Tokenization for plain text. This value indicates that tokens should be extracted from
786          * this property based on word breaks. Segments of whitespace and punctuation are not
787          * considered tokens.
788          *
789          * <p>For example, a property with "foo bar. baz." will produce tokens for "foo", "bar" and
790          * "baz". The segments " " and "." will not be considered tokens.
791          *
792          * <p>It is only valid for tokenizer_type to be 'PLAIN' if {@link #getIndexingType} is
793          * {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}.
794          */
795         public static final int TOKENIZER_TYPE_PLAIN = 1;
796 
797         /**
798          * This value indicates that no normalization or segmentation should be applied to string
799          * values that are tokenized using this type. Therefore, the output token is equivalent
800          * to the raw string value.
801          *
802          * <p>For example, a property with "Hello, world!" will produce the token "Hello, world!",
803          * preserving punctuation and capitalization, and not creating separate tokens between the
804          * space.
805          *
806          * <p>It is only valid for tokenizer_type to be 'VERBATIM' if {@link #getIndexingType} is
807          * {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}.
808          */
809         @RequiresFeature(
810                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
811                 name = Features.VERBATIM_SEARCH)
812         public static final int TOKENIZER_TYPE_VERBATIM = 2;
813 
814         /**
815          * Tokenization for emails. This value indicates that tokens should be extracted from
816          * this property based on email structure.
817          *
818          * <p>For example, a property with "alex.sav@google.com" will produce tokens for "alex",
819          * "sav", "alex.sav", "google", "com", and "alexsav@google.com"
820          *
821          * <p>It is only valid for tokenizer_type to be 'RFC822' if {@link #getIndexingType} is
822          * {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}.
823          */
824         @RequiresFeature(
825                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
826                 name = Features.TOKENIZER_TYPE_RFC822)
827         public static final int TOKENIZER_TYPE_RFC822 = 3;
828 
829         /**
830          * The joinable value type of the property. By setting the appropriate joinable value type
831          * for a property, the client can use the property for joining documents from other schema
832          * types using Search API (see {@link JoinSpec}).
833          * @exportToFramework:hide
834          */
835         // NOTE: The integer values of these constants must match the proto enum constants in
836         // com.google.android.icing.proto.JoinableConfig.ValueType.Code.
837         @IntDef(value = {
838                 JOINABLE_VALUE_TYPE_NONE,
839                 JOINABLE_VALUE_TYPE_QUALIFIED_ID,
840         })
841         @RestrictTo(RestrictTo.Scope.LIBRARY)
842         @Retention(RetentionPolicy.SOURCE)
843         public @interface JoinableValueType {
844         }
845 
846         /** Content in this property is not joinable. */
847         public static final int JOINABLE_VALUE_TYPE_NONE = 0;
848 
849         /**
850          * Content in this string property will be used as a qualified id to join documents.
851          * <ul>
852          *     <li>Qualified id: a unique identifier for a document, and this joinable value type is
853          *     similar to primary and foreign key in relational database. See
854          *     {@link androidx.appsearch.util.DocumentIdUtil} for more details.
855          *     <li>Currently we only support single string joining, so it should only be used with
856          *     {@link PropertyConfig#CARDINALITY_OPTIONAL} and
857          *     {@link PropertyConfig#CARDINALITY_REQUIRED}.
858          * </ul>
859          */
860         @RequiresFeature(
861                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
862                 name = Features.JOIN_SPEC_AND_QUALIFIED_ID)
863         public static final int JOINABLE_VALUE_TYPE_QUALIFIED_ID = 1;
864 
865         /**
866          * The delete propagation type of the property. By setting the delete propagation type for a
867          * property, the client can propagate deletion between the document and the referenced
868          * document. The propagation direction is determined by the delete propagation type.
869          *
870          * @exportToFramework:hide
871          */
872         // NOTE: The integer values of these constants must match the proto enum constants in
873         // com.google.android.icing.proto.JoinableConfig.DeletePropagationType.Code.
874         @IntDef(value = {
875                 DELETE_PROPAGATION_TYPE_NONE,
876                 DELETE_PROPAGATION_TYPE_PROPAGATE_FROM,
877         })
878         @RestrictTo(RestrictTo.Scope.LIBRARY)
879         @Retention(RetentionPolicy.SOURCE)
880         @ExperimentalAppSearchApi
881         public @interface DeletePropagationType {
882         }
883 
884         /** Does not propagate deletion. */
885         // TODO(b/384947619) unhide the API once it is ready.
886         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
887         @ExperimentalAppSearchApi
888         @FlaggedApi(Flags.FLAG_ENABLE_DELETE_PROPAGATION_TYPE)
889         public static final int DELETE_PROPAGATION_TYPE_NONE = 0;
890 
891         /**
892          * Content in this string property will be used as a qualified id referring to another
893          * (parent) document, and the deletion of the referenced document will propagate to this
894          * (child) document.
895          *
896          * <p>Please note that this propagates further. If the child document has any children that
897          * also set delete propagation type PROPAGATE_FROM for their joinable properties, then those
898          * (grandchild) documents will be deleted.
899          *
900          * <p>Since delete propagation works between the document and the referenced document, if
901          * setting this type for delete propagation, the string property should also be qualified id
902          * joinable (i.e. having {@link StringPropertyConfig#JOINABLE_VALUE_TYPE_QUALIFIED_ID} for
903          * the joinable value type). Otherwise, throw {@link IllegalStateException} when building
904          * (see {@link StringPropertyConfig.Builder#build}).
905          */
906         // TODO(b/384947619) unhide the API once it is ready.
907         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
908         @RequiresFeature(
909                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
910                 name = Features
911                         .SCHEMA_STRING_PROPERTY_CONFIG_DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
912         @ExperimentalAppSearchApi
913         @FlaggedApi(Flags.FLAG_ENABLE_DELETE_PROPAGATION_TYPE)
914         public static final int DELETE_PROPAGATION_TYPE_PROPAGATE_FROM = 1;
915 
StringPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)916         StringPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
917             super(propertyConfigParcel);
918         }
919 
920         /** Returns how the property is indexed. */
921         @StringPropertyConfig.IndexingType
getIndexingType()922         public int getIndexingType() {
923             StringIndexingConfigParcel indexingConfigParcel =
924                     mPropertyConfigParcel.getStringIndexingConfigParcel();
925             if (indexingConfigParcel == null) {
926                 return INDEXING_TYPE_NONE;
927             }
928 
929             return indexingConfigParcel.getIndexingType();
930         }
931 
932         /** Returns how this property is tokenized (split into words). */
933         @TokenizerType
getTokenizerType()934         public int getTokenizerType() {
935             StringIndexingConfigParcel indexingConfigParcel =
936                     mPropertyConfigParcel.getStringIndexingConfigParcel();
937             if (indexingConfigParcel == null) {
938                 return TOKENIZER_TYPE_NONE;
939             }
940 
941             return indexingConfigParcel.getTokenizerType();
942         }
943 
944         /**
945          * Returns how this property is going to be used to join documents from other schema types.
946          */
947         @JoinableValueType
getJoinableValueType()948         public int getJoinableValueType() {
949             JoinableConfigParcel joinableConfigParcel = mPropertyConfigParcel
950                     .getJoinableConfigParcel();
951             if (joinableConfigParcel == null) {
952                 return JOINABLE_VALUE_TYPE_NONE;
953             }
954 
955             return joinableConfigParcel.getJoinableValueType();
956         }
957 
958         /**
959          * Returns how the deletion will be propagated between this document and the referenced
960          * document whose qualified id is held by this property.
961          */
962         // TODO(b/384947619) unhide the API once it is ready.
963         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
964         @FlaggedApi(Flags.FLAG_ENABLE_DELETE_PROPAGATION_TYPE)
965         @ExperimentalAppSearchApi
966         @DeletePropagationType
getDeletePropagationType()967         public int getDeletePropagationType() {
968             JoinableConfigParcel joinableConfigParcel = mPropertyConfigParcel
969                     .getJoinableConfigParcel();
970             if (joinableConfigParcel == null) {
971                 return DELETE_PROPAGATION_TYPE_NONE;
972             }
973 
974             return joinableConfigParcel.getDeletePropagationType();
975         }
976 
977         /** Builder for {@link StringPropertyConfig}. */
978         @OptIn(markerClass = ExperimentalAppSearchApi.class)
979         public static final class Builder {
980             private final String mPropertyName;
981             private String mDescription = "";
982             @Cardinality
983             private int mCardinality = CARDINALITY_OPTIONAL;
984             @StringPropertyConfig.IndexingType
985             private int mIndexingType = INDEXING_TYPE_NONE;
986             @TokenizerType
987             private int mTokenizerType = TOKENIZER_TYPE_NONE;
988             @JoinableValueType
989             private int mJoinableValueType = JOINABLE_VALUE_TYPE_NONE;
990             @DeletePropagationType
991             private int mDeletePropagationType = DELETE_PROPAGATION_TYPE_NONE;
992 
993             /** Creates a new {@link StringPropertyConfig.Builder}. */
Builder(@onNull String propertyName)994             public Builder(@NonNull String propertyName) {
995                 mPropertyName = Preconditions.checkNotNull(propertyName);
996             }
997 
998             /**
999              * Sets a natural language description of this property.
1000              *
1001              * <p> For more details about the description field, see {@link
1002              * AppSearchSchema.PropertyConfig#getDescription}.
1003              */
1004             @CanIgnoreReturnValue
1005             @RequiresFeature(
1006                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1007                     name = Features.SCHEMA_SET_DESCRIPTION)
1008             @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
1009             @ExperimentalAppSearchApi
1010             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
setDescription( @onNull String description)1011             public @NonNull StringPropertyConfig.Builder setDescription(
1012                     @NonNull String description) {
1013                 mDescription = Preconditions.checkNotNull(description);
1014                 return this;
1015             }
1016 
1017             /**
1018              * Sets the cardinality of the property (whether it is optional, required or repeated).
1019              *
1020              * <p>If this method is not called, the default cardinality is
1021              * {@link PropertyConfig#CARDINALITY_OPTIONAL}.
1022              */
1023             @CanIgnoreReturnValue
1024             @SuppressWarnings("MissingGetterMatchingBuilder")  // getter defined in superclass
setCardinality( @ardinality int cardinality)1025             public @NonNull StringPropertyConfig.Builder setCardinality(
1026                     @Cardinality int cardinality) {
1027                 Preconditions.checkArgumentInRange(
1028                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
1029                 mCardinality = cardinality;
1030                 return this;
1031             }
1032 
1033             /**
1034              * Configures how a property should be indexed so that it can be retrieved by queries.
1035              *
1036              * <p>If this method is not called, the default indexing type is
1037              * {@link StringPropertyConfig#INDEXING_TYPE_NONE}, so that it cannot be matched by
1038              * queries.
1039              */
1040             @CanIgnoreReturnValue
setIndexingType( @tringPropertyConfig.IndexingType int indexingType)1041             public @NonNull StringPropertyConfig.Builder setIndexingType(
1042                     @StringPropertyConfig.IndexingType int indexingType) {
1043                 Preconditions.checkArgumentInRange(
1044                         indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_PREFIXES, "indexingType");
1045                 mIndexingType = indexingType;
1046                 return this;
1047             }
1048 
1049             /**
1050              * Configures how this property should be tokenized (split into words).
1051              *
1052              * <p>If this method is not called, the default indexing type is
1053              * {@link StringPropertyConfig#TOKENIZER_TYPE_NONE}, so that it is not tokenized.
1054              *
1055              * <p>This method must be called with a value other than
1056              * {@link StringPropertyConfig#TOKENIZER_TYPE_NONE} if the property is indexed (that is,
1057              * if {@link #setIndexingType} has been called with a value other than
1058              * {@link StringPropertyConfig#INDEXING_TYPE_NONE}).
1059              */
1060             @CanIgnoreReturnValue
setTokenizerType( @okenizerType int tokenizerType)1061             public @NonNull StringPropertyConfig.Builder setTokenizerType(
1062                     @TokenizerType int tokenizerType) {
1063                 Preconditions.checkArgumentInRange(
1064                         tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_RFC822, "tokenizerType");
1065                 mTokenizerType = tokenizerType;
1066                 return this;
1067             }
1068 
1069             /**
1070              * Configures how this property should be used as a joining matcher.
1071              *
1072              * <p>If this method is not called, the default joinable value type is
1073              * {@link StringPropertyConfig#JOINABLE_VALUE_TYPE_NONE}, so that it is not joinable.
1074              *
1075              * <p>At most, 64 properties can be set as joinable per schema.
1076              */
1077             @CanIgnoreReturnValue
setJoinableValueType( @oinableValueType int joinableValueType)1078             public @NonNull StringPropertyConfig.Builder setJoinableValueType(
1079                     @JoinableValueType int joinableValueType) {
1080                 Preconditions.checkArgumentInRange(
1081                         joinableValueType,
1082                         JOINABLE_VALUE_TYPE_NONE,
1083                         JOINABLE_VALUE_TYPE_QUALIFIED_ID,
1084                         "joinableValueType");
1085                 mJoinableValueType = joinableValueType;
1086                 return this;
1087             }
1088 
1089             /**
1090              * Configures how the deletion will be propagated between this document and the
1091              * referenced document whose qualified id is held by this property.
1092              *
1093              * <p>If this method is not called, the default delete propagation type is
1094              * {@link StringPropertyConfig#DELETE_PROPAGATION_TYPE_NONE}, indicating that deletion
1095              * will not propagate between this document and the referenced document.
1096              *
1097              * <p>If the delete propagation type is not
1098              * {@link StringPropertyConfig#DELETE_PROPAGATION_TYPE_NONE}, then
1099              * {@link StringPropertyConfig#JOINABLE_VALUE_TYPE_QUALIFIED_ID} must also be set since
1100              * the delete propagation has to use the qualified id. Otherwise, throw
1101              * {@link IllegalStateException} when building.
1102              */
1103             // TODO(b/384947619) unhide the API once it is ready.
1104             @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
1105             @CanIgnoreReturnValue
1106             @RequiresFeature(
1107                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1108                     name = Features
1109                             .SCHEMA_STRING_PROPERTY_CONFIG_DELETE_PROPAGATION_TYPE_PROPAGATE_FROM)
1110             @FlaggedApi(Flags.FLAG_ENABLE_DELETE_PROPAGATION_TYPE)
1111             @ExperimentalAppSearchApi
setDeletePropagationType( @eletePropagationType int deletePropagationType)1112             public @NonNull StringPropertyConfig.Builder setDeletePropagationType(
1113                     @DeletePropagationType int deletePropagationType) {
1114                 Preconditions.checkArgumentInRange(
1115                         deletePropagationType,
1116                         DELETE_PROPAGATION_TYPE_NONE,
1117                         DELETE_PROPAGATION_TYPE_PROPAGATE_FROM,
1118                         "deletePropagationType");
1119                 mDeletePropagationType = deletePropagationType;
1120                 return this;
1121             }
1122 
1123             /**
1124              * Constructs a new {@link StringPropertyConfig} from the contents of this builder.
1125              *
1126              * @throws IllegalStateException if any following condition:
1127              * <ul>
1128              *     <li>Tokenizer type is not {@link StringPropertyConfig#TOKENIZER_TYPE_NONE} with
1129              *     indexing type {@link StringPropertyConfig#INDEXING_TYPE_NONE}.
1130              *     <li>Indexing type is not {@link StringPropertyConfig#INDEXING_TYPE_NONE} with
1131              *     tokenizer type {@link StringPropertyConfig#TOKENIZER_TYPE_NONE}.
1132              *     <li>{@link StringPropertyConfig#JOINABLE_VALUE_TYPE_QUALIFIED_ID} is set to a
1133              *     {@link PropertyConfig#CARDINALITY_REPEATED} property.
1134              *     <li>Deletion type other than
1135              *     {@link StringPropertyConfig#DELETE_PROPAGATION_TYPE_NONE} is used without setting
1136              *     {@link StringPropertyConfig#JOINABLE_VALUE_TYPE_QUALIFIED_ID}.
1137              * </ul>
1138              */
build()1139             public @NonNull StringPropertyConfig build() {
1140                 if (mTokenizerType == TOKENIZER_TYPE_NONE) {
1141                     Preconditions.checkState(mIndexingType == INDEXING_TYPE_NONE, "Cannot set "
1142                             + "TOKENIZER_TYPE_NONE with an indexing type other than "
1143                             + "INDEXING_TYPE_NONE.");
1144                 } else {
1145                     Preconditions.checkState(mIndexingType != INDEXING_TYPE_NONE, "Cannot set "
1146                             + "TOKENIZER_TYPE_PLAIN with INDEXING_TYPE_NONE.");
1147                 }
1148                 if (mJoinableValueType == JOINABLE_VALUE_TYPE_QUALIFIED_ID) {
1149                     Preconditions.checkState(mCardinality != CARDINALITY_REPEATED, "Cannot set "
1150                             + "JOINABLE_VALUE_TYPE_QUALIFIED_ID with CARDINALITY_REPEATED.");
1151                 }
1152                 if (mDeletePropagationType != DELETE_PROPAGATION_TYPE_NONE) {
1153                     Preconditions.checkState(mJoinableValueType == JOINABLE_VALUE_TYPE_QUALIFIED_ID,
1154                             "Cannot set delete propagation without setting "
1155                             + "JOINABLE_VALUE_TYPE_QUALIFIED_ID.");
1156                 }
1157                 PropertyConfigParcel.StringIndexingConfigParcel stringConfigParcel =
1158                         new StringIndexingConfigParcel(mIndexingType, mTokenizerType);
1159                 JoinableConfigParcel joinableConfigParcel =
1160                         new JoinableConfigParcel(mJoinableValueType, mDeletePropagationType);
1161                 return new StringPropertyConfig(
1162                         PropertyConfigParcel.createForString(
1163                                 mPropertyName,
1164                                 mDescription,
1165                                 mCardinality,
1166                                 stringConfigParcel,
1167                                 joinableConfigParcel));
1168             }
1169         }
1170 
1171         /**
1172          * Appends a debug string for the {@link StringPropertyConfig} instance to the given
1173          * string builder.
1174          *
1175          * <p>This appends fields specific to a {@link StringPropertyConfig} instance.
1176          *
1177          * @param builder the builder to append to.
1178          */
1179         @OptIn(markerClass = ExperimentalAppSearchApi.class)
appendStringPropertyConfigFields(@onNull IndentingStringBuilder builder)1180         void appendStringPropertyConfigFields(@NonNull IndentingStringBuilder builder) {
1181             switch (getIndexingType()) {
1182                 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE:
1183                     builder.append("indexingType: INDEXING_TYPE_NONE,\n");
1184                     break;
1185                 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS:
1186                     builder.append("indexingType: INDEXING_TYPE_EXACT_TERMS,\n");
1187                     break;
1188                 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES:
1189                     builder.append("indexingType: INDEXING_TYPE_PREFIXES,\n");
1190                     break;
1191                 default:
1192                     builder.append("indexingType: INDEXING_TYPE_UNKNOWN,\n");
1193             }
1194 
1195             switch (getTokenizerType()) {
1196                 case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE:
1197                     builder.append("tokenizerType: TOKENIZER_TYPE_NONE,\n");
1198                     break;
1199                 case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN:
1200                     builder.append("tokenizerType: TOKENIZER_TYPE_PLAIN,\n");
1201                     break;
1202                 case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_VERBATIM:
1203                     builder.append("tokenizerType: TOKENIZER_TYPE_VERBATIM,\n");
1204                     break;
1205                 case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_RFC822:
1206                     builder.append("tokenizerType: TOKENIZER_TYPE_RFC822,\n");
1207                     break;
1208                 default:
1209                     builder.append("tokenizerType: TOKENIZER_TYPE_UNKNOWN,\n");
1210             }
1211 
1212             switch (getJoinableValueType()) {
1213                 case AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE:
1214                     builder.append("joinableValueType: JOINABLE_VALUE_TYPE_NONE,\n");
1215                     break;
1216                 case AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_QUALIFIED_ID:
1217                     builder.append("joinableValueType: JOINABLE_VALUE_TYPE_QUALIFIED_ID,\n");
1218                     break;
1219                 default:
1220                     builder.append("joinableValueType: JOINABLE_VALUE_TYPE_UNKNOWN,\n");
1221             }
1222 
1223             switch (getDeletePropagationType()) {
1224                 case StringPropertyConfig.DELETE_PROPAGATION_TYPE_NONE:
1225                     builder.append("deletePropagationType: DELETE_PROPAGATION_TYPE_NONE,\n");
1226                     break;
1227                 case StringPropertyConfig.DELETE_PROPAGATION_TYPE_PROPAGATE_FROM:
1228                     builder.append(
1229                             "deletePropagationType: DELETE_PROPAGATION_TYPE_PROPAGATE_FROM,\n");
1230                     break;
1231                 default:
1232                     builder.append("deletePropagationType: DELETE_PROPAGATION_TYPE_UNKNOWN,\n");
1233             }
1234         }
1235     }
1236 
1237     /** Configuration for a property containing a 64-bit integer. */
1238     public static final class LongPropertyConfig extends PropertyConfig {
1239         /**
1240          * Encapsulates the configurations on how AppSearch should query/index these 64-bit
1241          * integers.
1242          *
1243          * @exportToFramework:hide
1244          */
1245         @IntDef(value = {
1246                 INDEXING_TYPE_NONE,
1247                 INDEXING_TYPE_RANGE
1248         })
1249         @RestrictTo(RestrictTo.Scope.LIBRARY)
1250         @Retention(RetentionPolicy.SOURCE)
1251         public @interface IndexingType {
1252         }
1253 
1254         /** Content in this property will not be indexed. */
1255         public static final int INDEXING_TYPE_NONE = 0;
1256 
1257         /**
1258          * Content in this property will be indexed and can be fetched via numeric search range
1259          * query.
1260          *
1261          * <p>For example, a property with 1024 should match numeric search range query [0, 2000].
1262          */
1263         @RequiresFeature(
1264                 enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1265                 name = Features.NUMERIC_SEARCH)
1266         public static final int INDEXING_TYPE_RANGE = 1;
1267 
LongPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1268         LongPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
1269             super(propertyConfigParcel);
1270         }
1271 
1272         /** Returns how the property is indexed. */
1273         @LongPropertyConfig.IndexingType
getIndexingType()1274         public int getIndexingType() {
1275             PropertyConfigParcel.IntegerIndexingConfigParcel indexingConfigParcel =
1276                     mPropertyConfigParcel.getIntegerIndexingConfigParcel();
1277             if (indexingConfigParcel == null) {
1278                 return INDEXING_TYPE_NONE;
1279             }
1280             return indexingConfigParcel.getIndexingType();
1281         }
1282 
1283         /** Returns if the property is enabled for scoring. */
1284         @ExperimentalAppSearchApi
1285         @FlaggedApi(Flags.FLAG_ENABLE_SCORABLE_PROPERTY)
isScoringEnabled()1286         public boolean isScoringEnabled() {
1287             return mPropertyConfigParcel.isScoringEnabled();
1288         }
1289 
1290         /** Builder for {@link LongPropertyConfig}. */
1291         public static final class Builder {
1292             private final String mPropertyName;
1293             private String mDescription = "";
1294             @Cardinality
1295             private int mCardinality = CARDINALITY_OPTIONAL;
1296             @LongPropertyConfig.IndexingType
1297             private int mIndexingType = INDEXING_TYPE_NONE;
1298             private boolean mScoringEnabled = false;
1299 
1300             /** Creates a new {@link LongPropertyConfig.Builder}. */
Builder(@onNull String propertyName)1301             public Builder(@NonNull String propertyName) {
1302                 mPropertyName = Preconditions.checkNotNull(propertyName);
1303             }
1304 
1305             /**
1306              * Sets a natural language description of this property.
1307              *
1308              * <p> For more details about the description field, see {@link
1309              * AppSearchSchema.PropertyConfig#getDescription}.
1310              */
1311             @CanIgnoreReturnValue
1312             @RequiresFeature(
1313                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1314                     name = Features.SCHEMA_SET_DESCRIPTION)
1315             @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
1316             @ExperimentalAppSearchApi
1317             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
setDescription(@onNull String description)1318             public @NonNull LongPropertyConfig.Builder setDescription(@NonNull String description) {
1319                 mDescription = Preconditions.checkNotNull(description);
1320                 return this;
1321             }
1322 
1323             /**
1324              * Sets the cardinality of the property (whether it is optional, required or repeated).
1325              *
1326              * <p>If this method is not called, the default cardinality is
1327              * {@link PropertyConfig#CARDINALITY_OPTIONAL}.
1328              */
1329             @CanIgnoreReturnValue
1330             @SuppressWarnings("MissingGetterMatchingBuilder")  // getter defined in superclass
setCardinality( @ardinality int cardinality)1331             public @NonNull LongPropertyConfig.Builder setCardinality(
1332                     @Cardinality int cardinality) {
1333                 Preconditions.checkArgumentInRange(
1334                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
1335                 mCardinality = cardinality;
1336                 return this;
1337             }
1338 
1339             /**
1340              * Configures how a property should be indexed so that it can be retrieved by queries.
1341              *
1342              * <p>If this method is not called, the default indexing type is
1343              * {@link LongPropertyConfig#INDEXING_TYPE_NONE}, so that it will not be indexed
1344              * and cannot be matched by queries.
1345              */
1346             @CanIgnoreReturnValue
setIndexingType( @ongPropertyConfig.IndexingType int indexingType)1347             public @NonNull LongPropertyConfig.Builder setIndexingType(
1348                     @LongPropertyConfig.IndexingType int indexingType) {
1349                 Preconditions.checkArgumentInRange(
1350                         indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_RANGE, "indexingType");
1351                 mIndexingType = indexingType;
1352                 return this;
1353             }
1354 
1355             /**
1356              * Sets the property enabled or disabled for scoring.
1357              *
1358              * <p>If this method is not called, the default value is false.
1359              *
1360              * <p>If enabled, it can be used in the advanced ranking expression via the function of
1361              * 'getScorableProperty'.
1362              *
1363              * <p>For the detailed documentation, see
1364              * {@link SearchSpec.Builder#setRankingStrategy(String)}.
1365              */
1366             @CanIgnoreReturnValue
1367             @RequiresFeature(
1368                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1369                     name = Features.SCHEMA_SCORABLE_PROPERTY_CONFIG)
1370             @ExperimentalAppSearchApi
1371             @FlaggedApi(Flags.FLAG_ENABLE_SCORABLE_PROPERTY)
setScoringEnabled( boolean scoringEnabled)1372             public @NonNull LongPropertyConfig.Builder setScoringEnabled(
1373                     boolean scoringEnabled) {
1374                 mScoringEnabled = scoringEnabled;
1375                 return this;
1376             }
1377 
1378             /** Constructs a new {@link LongPropertyConfig} from the contents of this builder. */
build()1379             public @NonNull LongPropertyConfig build() {
1380                 return new LongPropertyConfig(
1381                         PropertyConfigParcel.createForLong(
1382                                 mPropertyName, mDescription, mCardinality, mIndexingType,
1383                                 mScoringEnabled));
1384             }
1385         }
1386 
1387         /**
1388          * Appends a debug string for the {@link LongPropertyConfig} instance to the given
1389          * string builder.
1390          *
1391          * <p>This appends fields specific to a {@link LongPropertyConfig} instance.
1392          *
1393          * @param builder the builder to append to.
1394          */
appendLongPropertyConfigFields(@onNull IndentingStringBuilder builder)1395         void appendLongPropertyConfigFields(@NonNull IndentingStringBuilder builder) {
1396             switch (getIndexingType()) {
1397                 case AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE:
1398                     builder.append("indexingType: INDEXING_TYPE_NONE,\n");
1399                     break;
1400                 case AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE:
1401                     builder.append("indexingType: INDEXING_TYPE_RANGE,\n");
1402                     break;
1403                 default:
1404                     builder.append("indexingType: INDEXING_TYPE_UNKNOWN,\n");
1405             }
1406         }
1407     }
1408 
1409     /** Configuration for a property containing a double-precision decimal number. */
1410     public static final class DoublePropertyConfig extends PropertyConfig {
DoublePropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1411         DoublePropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
1412             super(propertyConfigParcel);
1413         }
1414 
1415         /** Returns if the property is enabled for scoring. */
1416         @ExperimentalAppSearchApi
1417         @FlaggedApi(Flags.FLAG_ENABLE_SCORABLE_PROPERTY)
isScoringEnabled()1418         public boolean isScoringEnabled() {
1419             return mPropertyConfigParcel.isScoringEnabled();
1420         }
1421 
1422         /** Builder for {@link DoublePropertyConfig}. */
1423         public static final class Builder {
1424             private final String mPropertyName;
1425             private String mDescription = "";
1426             @Cardinality
1427             private int mCardinality = CARDINALITY_OPTIONAL;
1428             private boolean mScoringEnabled = false;
1429 
1430             /** Creates a new {@link DoublePropertyConfig.Builder}. */
Builder(@onNull String propertyName)1431             public Builder(@NonNull String propertyName) {
1432                 mPropertyName = Preconditions.checkNotNull(propertyName);
1433             }
1434 
1435             /**
1436              * Sets a natural language description of this property.
1437              *
1438              * <p> For more details about the description field, see {@link
1439              * AppSearchSchema.PropertyConfig#getDescription}.
1440              */
1441             @CanIgnoreReturnValue
1442             @RequiresFeature(
1443                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1444                     name = Features.SCHEMA_SET_DESCRIPTION)
1445             @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
1446             @ExperimentalAppSearchApi
1447             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
setDescription( @onNull String description)1448             public @NonNull DoublePropertyConfig.Builder setDescription(
1449                     @NonNull String description) {
1450                 mDescription = Preconditions.checkNotNull(description);
1451                 return this;
1452             }
1453 
1454             /**
1455              * Sets the cardinality of the property (whether it is optional, required or repeated).
1456              *
1457              * <p>If this method is not called, the default cardinality is
1458              * {@link PropertyConfig#CARDINALITY_OPTIONAL}.
1459              */
1460             @CanIgnoreReturnValue
1461             @SuppressWarnings("MissingGetterMatchingBuilder")  // getter defined in superclass
setCardinality( @ardinality int cardinality)1462             public @NonNull DoublePropertyConfig.Builder setCardinality(
1463                     @Cardinality int cardinality) {
1464                 Preconditions.checkArgumentInRange(
1465                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
1466                 mCardinality = cardinality;
1467                 return this;
1468             }
1469 
1470             /**
1471              * Sets the property enabled or disabled for scoring.
1472              *
1473              * <p>If this method is not called, the default value is false.
1474              *
1475              * <p>If enabled, it can be used in the advanced ranking expression via the function of
1476              * 'getScorableProperty'.
1477              *
1478              * <p>For the detailed documentation, see
1479              * {@link SearchSpec.Builder#setRankingStrategy(String)}.
1480              */
1481             @CanIgnoreReturnValue
1482             @RequiresFeature(
1483                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1484                     name = Features.SCHEMA_SCORABLE_PROPERTY_CONFIG)
1485             @ExperimentalAppSearchApi
1486             @FlaggedApi(Flags.FLAG_ENABLE_SCORABLE_PROPERTY)
setScoringEnabled( boolean scoringEnabled)1487             public @NonNull DoublePropertyConfig.Builder setScoringEnabled(
1488                     boolean scoringEnabled) {
1489                 mScoringEnabled = scoringEnabled;
1490                 return this;
1491             }
1492 
1493             /** Constructs a new {@link DoublePropertyConfig} from the contents of this builder. */
build()1494             public @NonNull DoublePropertyConfig build() {
1495                 return new DoublePropertyConfig(
1496                         PropertyConfigParcel.createForDouble(
1497                                 mPropertyName, mDescription, mCardinality, mScoringEnabled));
1498             }
1499         }
1500     }
1501 
1502     /** Configuration for a property containing a boolean. */
1503     public static final class BooleanPropertyConfig extends PropertyConfig {
BooleanPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1504         BooleanPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
1505             super(propertyConfigParcel);
1506         }
1507 
1508         /** Returns if the property is enabled for scoring. */
1509         @ExperimentalAppSearchApi
1510         @FlaggedApi(Flags.FLAG_ENABLE_SCORABLE_PROPERTY)
isScoringEnabled()1511         public boolean isScoringEnabled() {
1512             return mPropertyConfigParcel.isScoringEnabled();
1513         }
1514 
1515         /** Builder for {@link BooleanPropertyConfig}. */
1516         public static final class Builder {
1517             private final String mPropertyName;
1518             private String mDescription = "";
1519             @Cardinality
1520             private int mCardinality = CARDINALITY_OPTIONAL;
1521             private boolean mScoringEnabled = false;
1522 
1523             /** Creates a new {@link BooleanPropertyConfig.Builder}. */
Builder(@onNull String propertyName)1524             public Builder(@NonNull String propertyName) {
1525                 mPropertyName = Preconditions.checkNotNull(propertyName);
1526             }
1527 
1528             /**
1529               Sets a natural language description of this property.
1530              *
1531              * <p> For more details about the description field, see {@link
1532              * AppSearchSchema.PropertyConfig#getDescription}.
1533              */
1534             @CanIgnoreReturnValue
1535             @RequiresFeature(
1536                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1537                     name = Features.SCHEMA_SET_DESCRIPTION)
1538             @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
1539             @ExperimentalAppSearchApi
1540             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
setDescription( @onNull String description)1541             public @NonNull BooleanPropertyConfig.Builder setDescription(
1542                     @NonNull String description) {
1543                 mDescription = Preconditions.checkNotNull(description);
1544                 return this;
1545             }
1546 
1547             /**
1548              * Sets the cardinality of the property (whether it is optional, required or repeated).
1549              *
1550              * <p>If this method is not called, the default cardinality is
1551              * {@link PropertyConfig#CARDINALITY_OPTIONAL}.
1552              */
1553             @CanIgnoreReturnValue
1554             @SuppressWarnings("MissingGetterMatchingBuilder")  // getter defined in superclass
setCardinality( @ardinality int cardinality)1555             public @NonNull BooleanPropertyConfig.Builder setCardinality(
1556                     @Cardinality int cardinality) {
1557                 Preconditions.checkArgumentInRange(
1558                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
1559                 mCardinality = cardinality;
1560                 return this;
1561             }
1562 
1563             /**
1564              * Sets the property enabled or disabled for scoring.
1565              *
1566              * <p>If this method is not called, the default value is false.
1567              *
1568              * <p>If enabled, it can be used in the advanced ranking expression via the function of
1569              * 'getScorableProperty'.
1570              *
1571              * <p>For the detailed documentation, see
1572              * {@link SearchSpec.Builder#setRankingStrategy(String)}.
1573              */
1574             @CanIgnoreReturnValue
1575             @RequiresFeature(
1576                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1577                     name = Features.SCHEMA_SCORABLE_PROPERTY_CONFIG)
1578             @ExperimentalAppSearchApi
1579             @FlaggedApi(Flags.FLAG_ENABLE_SCORABLE_PROPERTY)
setScoringEnabled( boolean scoringEnabled)1580             public @NonNull BooleanPropertyConfig.Builder setScoringEnabled(
1581                     boolean scoringEnabled) {
1582                 mScoringEnabled = scoringEnabled;
1583                 return this;
1584             }
1585 
1586             /** Constructs a new {@link BooleanPropertyConfig} from the contents of this builder. */
build()1587             public @NonNull BooleanPropertyConfig build() {
1588                 return new BooleanPropertyConfig(
1589                         PropertyConfigParcel.createForBoolean(
1590                                 mPropertyName, mDescription, mCardinality, mScoringEnabled));
1591             }
1592         }
1593     }
1594 
1595     /** Configuration for a property containing a byte array. */
1596     public static final class BytesPropertyConfig extends PropertyConfig {
BytesPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1597         BytesPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
1598             super(propertyConfigParcel);
1599         }
1600 
1601         /** Builder for {@link BytesPropertyConfig}. */
1602         public static final class Builder {
1603             private final String mPropertyName;
1604             private String mDescription = "";
1605             @Cardinality
1606             private int mCardinality = CARDINALITY_OPTIONAL;
1607 
1608             /** Creates a new {@link BytesPropertyConfig.Builder}. */
Builder(@onNull String propertyName)1609             public Builder(@NonNull String propertyName) {
1610                 mPropertyName = Preconditions.checkNotNull(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             @RequiresFeature(
1621                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1622                     name = Features.SCHEMA_SET_DESCRIPTION)
1623             @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
1624             @ExperimentalAppSearchApi
1625             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
setDescription( @onNull String description)1626             public @NonNull BytesPropertyConfig.Builder setDescription(
1627                     @NonNull String description) {
1628                 mDescription = Preconditions.checkNotNull(description);
1629                 return this;
1630             }
1631 
1632             /**
1633              * Sets the cardinality of the property (whether it is optional, required or repeated).
1634              *
1635              * <p>If this method is not called, the default cardinality is
1636              * {@link PropertyConfig#CARDINALITY_OPTIONAL}.
1637              */
1638             @CanIgnoreReturnValue
1639             @SuppressWarnings("MissingGetterMatchingBuilder")  // getter defined in superclass
setCardinality( @ardinality int cardinality)1640             public @NonNull BytesPropertyConfig.Builder setCardinality(
1641                     @Cardinality int cardinality) {
1642                 Preconditions.checkArgumentInRange(
1643                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
1644                 mCardinality = cardinality;
1645                 return this;
1646             }
1647 
1648             /**
1649              * Constructs a new {@link BytesPropertyConfig} from the contents of this builder.
1650              */
build()1651             public @NonNull BytesPropertyConfig build() {
1652                 return new BytesPropertyConfig(
1653                         PropertyConfigParcel.createForBytes(
1654                                 mPropertyName, mDescription, mCardinality));
1655             }
1656         }
1657     }
1658 
1659     /** Configuration for a property containing another Document. */
1660     public static final class DocumentPropertyConfig extends PropertyConfig {
DocumentPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1661         DocumentPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
1662             super(propertyConfigParcel);
1663         }
1664 
1665         /** Returns the logical schema-type of the contents of this document property. */
getSchemaType()1666         public @NonNull String getSchemaType() {
1667             return Preconditions.checkNotNull(mPropertyConfigParcel.getSchemaType());
1668         }
1669 
1670         /**
1671          * Returns whether properties in the nested document should be indexed according to that
1672          * document's schema.
1673          *
1674          * <p>If false, the nested document's properties are not indexed regardless of its own
1675          * schema.
1676          *
1677          * @see DocumentPropertyConfig.Builder#addIndexableNestedProperties(Collection) for
1678          * indexing a subset of properties from the nested document.
1679          */
shouldIndexNestedProperties()1680         public boolean shouldIndexNestedProperties() {
1681             DocumentIndexingConfigParcel indexingConfigParcel =
1682                     mPropertyConfigParcel.getDocumentIndexingConfigParcel();
1683             if (indexingConfigParcel == null) {
1684                 return false;
1685             }
1686 
1687             return indexingConfigParcel.shouldIndexNestedProperties();
1688         }
1689 
1690         /**
1691          * Returns the list of indexable nested properties for the nested document.
1692          */
1693         @FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES)
getIndexableNestedProperties()1694         public @NonNull List<String> getIndexableNestedProperties() {
1695             DocumentIndexingConfigParcel indexingConfigParcel =
1696                     mPropertyConfigParcel.getDocumentIndexingConfigParcel();
1697             if (indexingConfigParcel == null) {
1698                 return Collections.emptyList();
1699             }
1700 
1701             List<String> indexableNestedPropertiesList =
1702                     indexingConfigParcel.getIndexableNestedPropertiesList();
1703             if (indexableNestedPropertiesList == null) {
1704                 return Collections.emptyList();
1705             }
1706 
1707             return Collections.unmodifiableList(indexableNestedPropertiesList);
1708         }
1709 
1710         /** Builder for {@link DocumentPropertyConfig}. */
1711         public static final class Builder {
1712             private final String mPropertyName;
1713             private final String mSchemaType;
1714             private String mDescription = "";
1715             @Cardinality
1716             private int mCardinality = CARDINALITY_OPTIONAL;
1717             private boolean mShouldIndexNestedProperties = false;
1718             private final Set<String> mIndexableNestedPropertiesList = new ArraySet<>();
1719 
1720             /**
1721              * Creates a new {@link DocumentPropertyConfig.Builder}.
1722              *
1723              * @param propertyName The logical name of the property in the schema, which will be
1724              *                     used as the key for this property in
1725              *                     {@link GenericDocument.Builder#setPropertyDocument}.
1726              * @param schemaType   The type of documents which will be stored in this property.
1727              *                     Documents of different types cannot be mixed into a single
1728              *                     property.
1729              */
Builder(@onNull String propertyName, @NonNull String schemaType)1730             public Builder(@NonNull String propertyName, @NonNull String schemaType) {
1731                 mPropertyName = Preconditions.checkNotNull(propertyName);
1732                 mSchemaType = Preconditions.checkNotNull(schemaType);
1733             }
1734 
1735             /**
1736              * Sets a natural language description of this property.
1737              *
1738              * <p> For more details about the description field, see {@link
1739              * AppSearchSchema.PropertyConfig#getDescription}.
1740              */
1741             @CanIgnoreReturnValue
1742             @RequiresFeature(
1743                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1744                     name = Features.SCHEMA_SET_DESCRIPTION)
1745             @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
1746             @ExperimentalAppSearchApi
1747             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
setDescription( @onNull String description)1748             public @NonNull DocumentPropertyConfig.Builder setDescription(
1749                     @NonNull String description) {
1750                 mDescription = Preconditions.checkNotNull(description);
1751                 return this;
1752             }
1753 
1754             /**
1755              * Sets the cardinality of the property (whether it is optional, required or repeated).
1756              *
1757              * <p>If this method is not called, the default cardinality is
1758              * {@link PropertyConfig#CARDINALITY_OPTIONAL}.
1759              */
1760             @CanIgnoreReturnValue
1761             @SuppressWarnings("MissingGetterMatchingBuilder")  // getter defined in superclass
setCardinality( @ardinality int cardinality)1762             public @NonNull DocumentPropertyConfig.Builder setCardinality(
1763                     @Cardinality int cardinality) {
1764                 Preconditions.checkArgumentInRange(
1765                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
1766                 mCardinality = cardinality;
1767                 return this;
1768             }
1769 
1770             /**
1771              * Configures whether properties in the nested document should be indexed according to
1772              * that document's schema.
1773              *
1774              * <p>If false, the nested document's properties are not indexed regardless of its own
1775              * schema.
1776              *
1777              * <p>To index a subset of properties from the nested document, set this to false and
1778              * use {@link #addIndexableNestedProperties(Collection)}.
1779              */
1780             @CanIgnoreReturnValue
setShouldIndexNestedProperties( boolean indexNestedProperties)1781             public @NonNull DocumentPropertyConfig.Builder setShouldIndexNestedProperties(
1782                     boolean indexNestedProperties) {
1783                 mShouldIndexNestedProperties = indexNestedProperties;
1784                 return this;
1785             }
1786 
1787             /**
1788              * Adds one or more properties for indexing from the nested document property.
1789              *
1790              * @see #addIndexableNestedProperties(Collection)
1791              */
1792             @FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES)
1793             @CanIgnoreReturnValue
1794             @RequiresFeature(
1795                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1796                     name = Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES)
addIndexableNestedProperties( @onNull String... indexableNestedProperties)1797             public @NonNull DocumentPropertyConfig.Builder addIndexableNestedProperties(
1798                     @NonNull String... indexableNestedProperties) {
1799                 Preconditions.checkNotNull(indexableNestedProperties);
1800                 return addIndexableNestedProperties(Arrays.asList(indexableNestedProperties));
1801             }
1802 
1803             /**
1804              * Adds one or more property paths for indexing from the nested document property.
1805              *
1806              * @see #addIndexableNestedProperties(Collection)
1807              */
1808             @FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES)
1809             @CanIgnoreReturnValue
1810             @SuppressLint("MissingGetterMatchingBuilder")
1811             @RequiresFeature(
1812                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1813                     name = Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES)
addIndexableNestedPropertyPaths( @onNull PropertyPath... indexableNestedPropertyPaths)1814             public @NonNull DocumentPropertyConfig.Builder addIndexableNestedPropertyPaths(
1815                     @NonNull PropertyPath... indexableNestedPropertyPaths) {
1816                 Preconditions.checkNotNull(indexableNestedPropertyPaths);
1817                 return addIndexableNestedPropertyPaths(Arrays.asList(indexableNestedPropertyPaths));
1818             }
1819 
1820             /**
1821              * Adds one or more properties for indexing from the nested document. The added property
1822              * will be indexed according to that property's indexing configurations in the
1823              * document's schema definition. All properties in this list will consume a sectionId
1824              * regardless of its actual indexing config -- this includes properties added that
1825              * do not actually exist, as well as properties that are not set as indexable in the
1826              * nested schema type.
1827              *
1828              * <p>Input strings should follow the format of the property path for the nested
1829              * property, with '.' as the path separator. This nested document's property name
1830              * should not be included in the property path.
1831              *
1832              * <p>Ex. Consider an 'Organization' schema type which defines a nested document
1833              * property 'address' (Address schema type), where Address has a nested document
1834              * property 'country' (Country schema type with string 'name' property), and a string
1835              * 'street' property. The 'street' and 'country's name' properties from the 'address'
1836              * document property can be indexed for the 'Organization' schema type by calling:
1837              * <pre>{@code
1838              * OrganizationSchema.addProperty(
1839              *                 new DocumentPropertyConfig.Builder("address", "Address")
1840              *                         .addIndexableNestedProperties("street", "country.name")
1841              *                         .build()).
1842              * }</pre>
1843              *
1844              * <p>{@link DocumentPropertyConfig.Builder#setShouldIndexNestedProperties} is
1845              * required to be false if any indexable nested property is added this way for the
1846              * document property. Attempting to build a DocumentPropertyConfig when this is not
1847              * true throws {@link IllegalArgumentException}.
1848              */
1849             @CanIgnoreReturnValue
1850             @RequiresFeature(
1851                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1852                     name = Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES)
addIndexableNestedProperties( @onNull Collection<String> indexableNestedProperties)1853             public @NonNull DocumentPropertyConfig.Builder addIndexableNestedProperties(
1854                     @NonNull Collection<String> indexableNestedProperties) {
1855                 Preconditions.checkNotNull(indexableNestedProperties);
1856                 mIndexableNestedPropertiesList.addAll(indexableNestedProperties);
1857                 return this;
1858             }
1859 
1860             /**
1861              * Adds one or more property paths for indexing from the nested document property.
1862              *
1863              * @see #addIndexableNestedProperties(Collection)
1864              */
1865             @FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES)
1866             @CanIgnoreReturnValue
1867             @SuppressLint("MissingGetterMatchingBuilder")
1868             @RequiresFeature(
1869                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1870                     name = Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES)
addIndexableNestedPropertyPaths( @onNull Collection<PropertyPath> indexableNestedPropertyPaths)1871             public @NonNull DocumentPropertyConfig.Builder addIndexableNestedPropertyPaths(
1872                     @NonNull Collection<PropertyPath> indexableNestedPropertyPaths) {
1873                 Preconditions.checkNotNull(indexableNestedPropertyPaths);
1874                 List<PropertyPath> propertyPathList = new ArrayList<>(indexableNestedPropertyPaths);
1875                 for (int i = 0; i < indexableNestedPropertyPaths.size(); i++) {
1876                     mIndexableNestedPropertiesList.add(propertyPathList.get(i).toString());
1877                 }
1878                 return this;
1879             }
1880 
1881             /**
1882              * Constructs a new {@link PropertyConfig} from the contents of this builder.
1883              *
1884              * @throws IllegalArgumentException if the provided PropertyConfig sets
1885              * {@link #shouldIndexNestedProperties()} to true and has one or more properties
1886              * defined using {@link #addIndexableNestedProperties(Collection)}.
1887              */
build()1888             public @NonNull DocumentPropertyConfig build() {
1889                 if (mShouldIndexNestedProperties && !mIndexableNestedPropertiesList.isEmpty()) {
1890                     throw new IllegalArgumentException(
1891                             "DocumentIndexingConfig#shouldIndexNestedProperties is required "
1892                                     + "to be false when one or more indexableNestedProperties are "
1893                                     + "provided.");
1894                 }
1895                 return new DocumentPropertyConfig(
1896                         PropertyConfigParcel.createForDocument(
1897                                 mPropertyName,
1898                                 mDescription,
1899                                 mCardinality,
1900                                 mSchemaType,
1901                                 new DocumentIndexingConfigParcel(mShouldIndexNestedProperties,
1902                                         new ArrayList<>(mIndexableNestedPropertiesList))));
1903             }
1904         }
1905 
1906         /**
1907          * Appends a debug string for the {@link DocumentPropertyConfig} instance to the given
1908          * string builder.
1909          *
1910          * <p>This appends fields specific to a {@link DocumentPropertyConfig} instance.
1911          *
1912          * @param builder the builder to append to.
1913          */
appendDocumentPropertyConfigFields(@onNull IndentingStringBuilder builder)1914         void appendDocumentPropertyConfigFields(@NonNull IndentingStringBuilder builder) {
1915             builder
1916                     .append("shouldIndexNestedProperties: ")
1917                     .append(shouldIndexNestedProperties())
1918                     .append(",\n");
1919 
1920             builder.append("indexableNestedProperties: ")
1921                     .append(getIndexableNestedProperties())
1922                     .append(",\n");
1923 
1924             builder.append("schemaType: \"").append(getSchemaType()).append("\",\n");
1925         }
1926     }
1927 
1928     /**
1929      * Configuration for a property of type {@link EmbeddingVector} in a Document.
1930      */
1931     @RequiresFeature(
1932             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
1933             name = Features.SCHEMA_EMBEDDING_PROPERTY_CONFIG)
1934     @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
1935     public static final class EmbeddingPropertyConfig extends PropertyConfig {
1936         /**
1937          * Encapsulates the configurations on how AppSearch should query/index these embedding
1938          * vectors.
1939          *
1940          * @exportToFramework:hide
1941          */
1942         @IntDef(value = {
1943                 INDEXING_TYPE_NONE,
1944                 INDEXING_TYPE_SIMILARITY
1945         })
1946         @RestrictTo(RestrictTo.Scope.LIBRARY)
1947         @Retention(RetentionPolicy.SOURCE)
1948         public @interface IndexingType {
1949         }
1950 
1951         /** Content in this property will not be indexed. */
1952         public static final int INDEXING_TYPE_NONE = 0;
1953 
1954         /**
1955          * Embedding vectors in this property will be indexed.
1956          *
1957          * <p>The index offers 100% accuracy, but has linear time complexity based on the number
1958          * of embedding vectors within the index.
1959          */
1960         public static final int INDEXING_TYPE_SIMILARITY = 1;
1961 
1962         /**
1963          * Indicates whether the vector contents of this property should be quantized.
1964          *
1965          * @exportToFramework:hide
1966          */
1967         @IntDef(value = {
1968                 QUANTIZATION_TYPE_NONE,
1969                 QUANTIZATION_TYPE_8_BIT,
1970         })
1971         @RestrictTo(RestrictTo.Scope.LIBRARY)
1972         @Retention(RetentionPolicy.SOURCE)
1973         @ExperimentalAppSearchApi
1974         public @interface QuantizationType {
1975         }
1976 
1977         /** Contents in this property will not be quantized. */
1978         @ExperimentalAppSearchApi
1979         public static final int QUANTIZATION_TYPE_NONE = 0;
1980 
1981         /** Contents in this property will be quantized to 8 bits. */
1982         @ExperimentalAppSearchApi
1983         public static final int QUANTIZATION_TYPE_8_BIT = 1;
1984 
EmbeddingPropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)1985         EmbeddingPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
1986             super(propertyConfigParcel);
1987         }
1988 
1989         /** Returns how the property is indexed. */
1990         @EmbeddingPropertyConfig.IndexingType
getIndexingType()1991         public int getIndexingType() {
1992             PropertyConfigParcel.EmbeddingIndexingConfigParcel indexingConfigParcel =
1993                     mPropertyConfigParcel.getEmbeddingIndexingConfigParcel();
1994             if (indexingConfigParcel == null) {
1995                 return INDEXING_TYPE_NONE;
1996             }
1997             return indexingConfigParcel.getIndexingType();
1998         }
1999 
2000         /**
2001          * Returns how the embedding contents of this property should be quantized.
2002          *
2003          * <p>If the property isn't indexed, returns {@link #QUANTIZATION_TYPE_NONE}.
2004          */
2005         @EmbeddingPropertyConfig.QuantizationType
2006         @ExperimentalAppSearchApi
2007         @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_QUANTIZATION)
getQuantizationType()2008         public int getQuantizationType() {
2009             PropertyConfigParcel.EmbeddingIndexingConfigParcel indexingConfigParcel =
2010                     mPropertyConfigParcel.getEmbeddingIndexingConfigParcel();
2011             if (indexingConfigParcel == null) {
2012                 return QUANTIZATION_TYPE_NONE;
2013             }
2014             return indexingConfigParcel.getQuantizationType();
2015         }
2016 
2017         /** Builder for {@link EmbeddingPropertyConfig}. */
2018         @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_PROPERTY_CONFIG)
2019         @OptIn(markerClass = ExperimentalAppSearchApi.class)
2020         public static final class Builder {
2021             private final String mPropertyName;
2022             private String mDescription = "";
2023             @Cardinality
2024             private int mCardinality = CARDINALITY_OPTIONAL;
2025             @EmbeddingPropertyConfig.IndexingType
2026             private int mIndexingType = INDEXING_TYPE_NONE;
2027             @EmbeddingPropertyConfig.QuantizationType
2028             private int mQuantizationType = QUANTIZATION_TYPE_NONE;
2029 
2030             /** Creates a new {@link EmbeddingPropertyConfig.Builder}. */
Builder(@onNull String propertyName)2031             public Builder(@NonNull String propertyName) {
2032                 mPropertyName = Preconditions.checkNotNull(propertyName);
2033             }
2034 
2035             /**
2036              * Sets a natural language description of this property.
2037              *
2038              * <p> For more details about the description field, see {@link
2039              * AppSearchSchema.PropertyConfig#getDescription}.
2040              */
2041             @CanIgnoreReturnValue
2042             @RequiresFeature(
2043                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2044                     name = Features.SCHEMA_SET_DESCRIPTION)
2045             @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
2046             @ExperimentalAppSearchApi
2047             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
setDescription( @onNull String description)2048             public @NonNull EmbeddingPropertyConfig.Builder setDescription(
2049                     @NonNull String description) {
2050                 mDescription = Preconditions.checkNotNull(description);
2051                 return this;
2052             }
2053 
2054             /**
2055              * Sets the cardinality of the property (whether it is optional, required or repeated).
2056              *
2057              * <p>If this method is not called, the default cardinality is
2058              * {@link PropertyConfig#CARDINALITY_OPTIONAL}.
2059              */
2060             @CanIgnoreReturnValue
2061             @SuppressWarnings("MissingGetterMatchingBuilder")  // getter defined in superclass
setCardinality( @ardinality int cardinality)2062             public @NonNull EmbeddingPropertyConfig.Builder setCardinality(
2063                     @Cardinality int cardinality) {
2064                 Preconditions.checkArgumentInRange(
2065                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
2066                 mCardinality = cardinality;
2067                 return this;
2068             }
2069 
2070             /**
2071              * Configures how a property should be indexed so that it can be retrieved by queries.
2072              *
2073              * <p>If this method is not called, the default indexing type is
2074              * {@link EmbeddingPropertyConfig#INDEXING_TYPE_NONE}, so that it will not be indexed
2075              * and cannot be matched by queries.
2076              */
2077             @CanIgnoreReturnValue
setIndexingType( @mbeddingPropertyConfig.IndexingType int indexingType)2078             public @NonNull EmbeddingPropertyConfig.Builder setIndexingType(
2079                     @EmbeddingPropertyConfig.IndexingType int indexingType) {
2080                 Preconditions.checkArgumentInRange(
2081                         indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_SIMILARITY,
2082                         "indexingType");
2083                 mIndexingType = indexingType;
2084                 return this;
2085             }
2086 
2087             /**
2088              * Configures whether the vector contents of this property should be quantized.
2089              *
2090              * <p>Quantization can reduce the size of the embedding search index, potentially
2091              * leading to faster embedding search due to lower I/O bandwidth. Quantization is
2092              * usually very reliable and in most cases will have a negligible impact on recall.
2093              * Using quantization is strongly recommended.
2094              *
2095              * <p>If this method is not called, the default quantization type is
2096              * {@link EmbeddingPropertyConfig#QUANTIZATION_TYPE_NONE}.
2097              */
2098             @ExperimentalAppSearchApi
2099             @RequiresFeature(
2100                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2101                     name = Features.SCHEMA_EMBEDDING_QUANTIZATION)
2102             @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_EMBEDDING_QUANTIZATION)
2103             @CanIgnoreReturnValue
setQuantizationType( @mbeddingPropertyConfig.QuantizationType int quantizationType)2104             public @NonNull EmbeddingPropertyConfig.Builder setQuantizationType(
2105                     @EmbeddingPropertyConfig.QuantizationType int quantizationType) {
2106                 Preconditions.checkArgumentInRange(
2107                         quantizationType, QUANTIZATION_TYPE_NONE, QUANTIZATION_TYPE_8_BIT,
2108                         "quantizationType");
2109                 mQuantizationType = quantizationType;
2110                 return this;
2111             }
2112 
2113             /**
2114              * Constructs a new {@link EmbeddingPropertyConfig} from the contents of this
2115              * builder.
2116              */
build()2117             public @NonNull EmbeddingPropertyConfig build() {
2118                 return new EmbeddingPropertyConfig(
2119                         PropertyConfigParcel.createForEmbedding(
2120                                 mPropertyName, mDescription, mCardinality, mIndexingType,
2121                                 mQuantizationType));
2122             }
2123         }
2124     }
2125 
2126     /**
2127      * Configuration for a property of type {@link AppSearchBlobHandle} in a Document.
2128      */
2129     @RequiresFeature(
2130             enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2131             name = Features.BLOB_STORAGE)
2132     @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE)
2133     @ExperimentalAppSearchApi
2134     public static final class BlobHandlePropertyConfig extends PropertyConfig {
BlobHandlePropertyConfig(@onNull PropertyConfigParcel propertyConfigParcel)2135         BlobHandlePropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
2136             super(propertyConfigParcel);
2137         }
2138 
2139         /** Builder for {@link BlobHandlePropertyConfig}. */
2140         @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE)
2141         public static final class Builder {
2142             private final String mPropertyName;
2143             private String mDescription = "";
2144             @Cardinality
2145             private int mCardinality = CARDINALITY_OPTIONAL;
2146 
2147             /** Creates a new {@link BlobHandlePropertyConfig.Builder}. */
Builder(@onNull String propertyName)2148             public Builder(@NonNull String propertyName) {
2149                 mPropertyName = Preconditions.checkNotNull(propertyName);
2150             }
2151 
2152             /**
2153              * Sets a natural language description of this property.
2154              *
2155              * <p> For more details about the description field, see {@link
2156              * AppSearchSchema.PropertyConfig#getDescription}.
2157              */
2158             @CanIgnoreReturnValue
2159             @RequiresFeature(
2160                     enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
2161                     name = Features.SCHEMA_SET_DESCRIPTION)
2162             @FlaggedApi(Flags.FLAG_ENABLE_SCHEMA_DESCRIPTION)
2163             @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
setDescription(@onNull String description)2164             public @NonNull Builder setDescription(@NonNull String description) {
2165                 mDescription = Preconditions.checkNotNull(description);
2166                 return this;
2167             }
2168 
2169             /**
2170              * Sets the cardinality of the property (whether it is optional, required or repeated).
2171              *
2172              * <p>If this method is not called, the default cardinality is
2173              * {@link PropertyConfig#CARDINALITY_OPTIONAL}.
2174              */
2175             @CanIgnoreReturnValue
2176             @SuppressWarnings("MissingGetterMatchingBuilder")  // getter defined in superclass
setCardinality(@ardinality int cardinality)2177             public @NonNull Builder setCardinality(@Cardinality int cardinality) {
2178                 Preconditions.checkArgumentInRange(
2179                         cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
2180                 mCardinality = cardinality;
2181                 return this;
2182             }
2183 
2184             /**
2185              * Constructs a new {@link BlobHandlePropertyConfig} from the contents of this
2186              * builder.
2187              */
build()2188             public @NonNull BlobHandlePropertyConfig build() {
2189                 return new BlobHandlePropertyConfig(
2190                         PropertyConfigParcel.createForBlobHandle(
2191                                 mPropertyName, mDescription, mCardinality));
2192             }
2193         }
2194     }
2195 }
2196