/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app.appsearch; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.appsearch.annotation.CanIgnoreReturnValue; import android.app.appsearch.exceptions.AppSearchException; import android.app.appsearch.exceptions.IllegalSchemaException; import android.app.appsearch.flags.Flags; import android.app.appsearch.safeparcel.AbstractSafeParcelable; import android.app.appsearch.safeparcel.PropertyConfigParcel; import android.app.appsearch.safeparcel.PropertyConfigParcel.DocumentIndexingConfigParcel; import android.app.appsearch.safeparcel.PropertyConfigParcel.JoinableConfigParcel; import android.app.appsearch.safeparcel.PropertyConfigParcel.StringIndexingConfigParcel; import android.app.appsearch.safeparcel.SafeParcelable; import android.app.appsearch.util.IndentingStringBuilder; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Set; /** * The AppSearch Schema for a particular type of document. * *
For example, an e-mail message or a music recording could be a schema type. * *
The schema consists of type information, properties, and config (like tokenization type).
*
* @see AppSearchSession#setSchema
*/
@SafeParcelable.Class(creator = "AppSearchSchemaCreator")
@SuppressWarnings("HiddenSuperclass")
public final class AppSearchSchema extends AbstractSafeParcelable {
@FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2)
@NonNull
public static final Parcelable.Creator Ex. The description for an Email type could be "A type of electronic message".
*
* This information is purely to help apps consuming this type to understand its semantic
* meaning. This field has no effect in AppSearch - it is just stored with the AppSearchSchema.
* If {@link Builder#setDescription} is uncalled, then this method will return an empty string.
*/
@FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS)
@NonNull
public String getDescription() {
return mDescription;
}
/**
* Returns the list of {@link PropertyConfig}s that are part of this schema.
*
* This method creates a new list when called.
*/
@NonNull
@SuppressWarnings({"MixedMutabilityReturnType"})
public List For more details about the description field, see {@link
* AppSearchSchema#getDescription}.
*/
@FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS)
@CanIgnoreReturnValue
@NonNull
public AppSearchSchema.Builder setDescription(@NonNull String description) {
Objects.requireNonNull(description);
resetIfBuilt();
mDescription = description;
return this;
}
/** Adds a property to the given type. */
@CanIgnoreReturnValue
@NonNull
public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
Objects.requireNonNull(propertyConfig);
resetIfBuilt();
String name = propertyConfig.getName();
if (!mPropertyNames.add(name)) {
throw new IllegalSchemaException("Property defined more than once: " + name);
}
mPropertyConfigParcels.add(propertyConfig.mPropertyConfigParcel);
return this;
}
/**
* Adds a parent type to the given type for polymorphism, so that the given type will be
* considered as a subtype of {@code parentSchemaType}.
*
* Subtype relations are automatically considered transitive, so callers are only
* required to provide direct parents. Specifically, if T1 <: T2 and T2 <: T3 are
* known, then T1 <: T3 will be inferred automatically, where <: is the subtype
* symbol.
*
* Polymorphism is currently supported in the following ways:
*
* Subtypes must meet the following requirements. A violation of the requirements will
* cause {@link AppSearchSession#setSchema} to throw an {@link AppSearchException} with the
* result code of {@link AppSearchResult#RESULT_INVALID_ARGUMENT}. Consider a type Artist
* and a type Person, and Artist claims to be a subtype of Person, then:
*
* A type can be defined to have multiple parents, but it must be compatible with each of
* its parents based on the above rules. For example, if LocalBusiness is defined as a
* subtype of both Place and Organization, then the compatibility of LocalBusiness with
* Place and the compatibility of LocalBusiness with Organization will both be checked.
*/
@CanIgnoreReturnValue
@NonNull
public AppSearchSchema.Builder addParentType(@NonNull String parentSchemaType) {
Objects.requireNonNull(parentSchemaType);
resetIfBuilt();
mParentTypes.add(parentSchemaType);
return this;
}
/** Constructs a new {@link AppSearchSchema} from the contents of this builder. */
@NonNull
public AppSearchSchema build() {
mBuilt = true;
return new AppSearchSchema(
mSchemaType,
mPropertyConfigParcels,
new ArrayList<>(mParentTypes),
mDescription);
}
private void resetIfBuilt() {
if (mBuilt) {
mPropertyConfigParcels = new ArrayList<>(mPropertyConfigParcels);
mParentTypes = new LinkedHashSet<>(mParentTypes);
mBuilt = false;
}
}
}
/**
* Common configuration for a single property (field) in a Document.
*
* For example, an {@code EmailMessage} would be a type and the {@code subject} would be a
* property.
*/
public abstract static class PropertyConfig {
/**
* Physical data-types of the contents of the property.
*
* NOTE: The integer values of these constants must match the proto enum constants in
* com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
*
* @hide
*/
@IntDef(
value = {
DATA_TYPE_STRING,
DATA_TYPE_LONG,
DATA_TYPE_DOUBLE,
DATA_TYPE_BOOLEAN,
DATA_TYPE_BYTES,
DATA_TYPE_DOCUMENT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DataType {}
/** @hide */
public static final int DATA_TYPE_STRING = 1;
/** @hide */
public static final int DATA_TYPE_LONG = 2;
/** @hide */
public static final int DATA_TYPE_DOUBLE = 3;
/** @hide */
public static final int DATA_TYPE_BOOLEAN = 4;
/**
* Unstructured BLOB.
*
* @hide
*/
public static final int DATA_TYPE_BYTES = 5;
/**
* Indicates that the property is itself a {@link GenericDocument}, making it part of a
* hierarchical schema. Any property using this DataType MUST have a valid {@link
* PropertyConfig#getSchemaType}.
*
* @hide
*/
public static final int DATA_TYPE_DOCUMENT = 6;
/**
* The cardinality of the property (whether it is required, optional or repeated).
*
* NOTE: The integer values of these constants must match the proto enum constants in
* com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
*
* @hide
*/
@IntDef(
value = {
CARDINALITY_REPEATED,
CARDINALITY_OPTIONAL,
CARDINALITY_REQUIRED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Cardinality {}
/** Any number of items (including zero) [0...*]. */
public static final int CARDINALITY_REPEATED = 1;
/** Zero or one value [0,1]. */
public static final int CARDINALITY_OPTIONAL = 2;
/** Exactly one value [1]. */
public static final int CARDINALITY_REQUIRED = 3;
final PropertyConfigParcel mPropertyConfigParcel;
PropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
mPropertyConfigParcel = Objects.requireNonNull(propertyConfigParcel);
}
@Override
@NonNull
public String toString() {
IndentingStringBuilder stringBuilder = new IndentingStringBuilder();
appendPropertyConfigString(stringBuilder);
return stringBuilder.toString();
}
/**
* Appends a debug string for the {@link AppSearchSchema.PropertyConfig} instance to the
* given string builder.
*
* @param builder the builder to append to.
*/
void appendPropertyConfigString(@NonNull IndentingStringBuilder builder) {
Objects.requireNonNull(builder);
builder.append("{\n");
builder.increaseIndentLevel();
builder.append("name: \"").append(getName()).append("\",\n");
builder.append("description: \"").append(getDescription()).append("\",\n");
if (this instanceof AppSearchSchema.StringPropertyConfig) {
((StringPropertyConfig) this).appendStringPropertyConfigFields(builder);
} else if (this instanceof AppSearchSchema.DocumentPropertyConfig) {
((DocumentPropertyConfig) this).appendDocumentPropertyConfigFields(builder);
} else if (this instanceof AppSearchSchema.LongPropertyConfig) {
((LongPropertyConfig) this).appendLongPropertyConfigFields(builder);
}
switch (getCardinality()) {
case AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED:
builder.append("cardinality: CARDINALITY_REPEATED,\n");
break;
case AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL:
builder.append("cardinality: CARDINALITY_OPTIONAL,\n");
break;
case AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED:
builder.append("cardinality: CARDINALITY_REQUIRED,\n");
break;
default:
builder.append("cardinality: CARDINALITY_UNKNOWN,\n");
}
switch (getDataType()) {
case AppSearchSchema.PropertyConfig.DATA_TYPE_STRING:
builder.append("dataType: DATA_TYPE_STRING,\n");
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_LONG:
builder.append("dataType: DATA_TYPE_LONG,\n");
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE:
builder.append("dataType: DATA_TYPE_DOUBLE,\n");
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN:
builder.append("dataType: DATA_TYPE_BOOLEAN,\n");
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES:
builder.append("dataType: DATA_TYPE_BYTES,\n");
break;
case AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT:
builder.append("dataType: DATA_TYPE_DOCUMENT,\n");
break;
default:
builder.append("dataType: DATA_TYPE_UNKNOWN,\n");
}
builder.decreaseIndentLevel();
builder.append("}");
}
/** Returns the name of this property. */
@NonNull
public String getName() {
return mPropertyConfigParcel.getName();
}
/**
* Returns a natural language description of this property.
*
* Ex. The description for the "homeAddress" property of a "Person" type could be "the
* address at which this person lives".
*
* This information is purely to help apps consuming this type the semantic meaning of
* its properties. This field has no effect in AppSearch - it is just stored with the
* AppSearchSchema. If the description is not set, then this method will return an empty
* string.
*/
@FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS)
@NonNull
public String getDescription() {
return mPropertyConfigParcel.getDescription();
}
/**
* Returns the type of data the property contains (such as string, int, bytes, etc).
*
* @hide
*/
@DataType
public int getDataType() {
return mPropertyConfigParcel.getDataType();
}
/**
* Returns the cardinality of the property (whether it is optional, required or repeated).
*/
@Cardinality
public int getCardinality() {
return mPropertyConfigParcel.getCardinality();
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof PropertyConfig)) {
return false;
}
PropertyConfig otherProperty = (PropertyConfig) other;
return Objects.equals(mPropertyConfigParcel, otherProperty.mPropertyConfigParcel);
}
@Override
public int hashCode() {
return mPropertyConfigParcel.hashCode();
}
/**
* Converts a {@link Bundle} into a {@link PropertyConfig} depending on its internal data
* type.
*
* The bundle is not cloned.
*
* @throws IllegalArgumentException if the bundle does no contain a recognized value in its
* {@code DATA_TYPE_FIELD}.
* @hide
*/
@NonNull
public static PropertyConfig fromParcel(
@NonNull PropertyConfigParcel propertyConfigParcel) {
Objects.requireNonNull(propertyConfigParcel);
switch (propertyConfigParcel.getDataType()) {
case PropertyConfig.DATA_TYPE_STRING:
return new StringPropertyConfig(propertyConfigParcel);
case PropertyConfig.DATA_TYPE_LONG:
return new LongPropertyConfig(propertyConfigParcel);
case PropertyConfig.DATA_TYPE_DOUBLE:
return new DoublePropertyConfig(propertyConfigParcel);
case PropertyConfig.DATA_TYPE_BOOLEAN:
return new BooleanPropertyConfig(propertyConfigParcel);
case PropertyConfig.DATA_TYPE_BYTES:
return new BytesPropertyConfig(propertyConfigParcel);
case PropertyConfig.DATA_TYPE_DOCUMENT:
return new DocumentPropertyConfig(propertyConfigParcel);
default:
throw new IllegalArgumentException(
"Unsupported property bundle of type "
+ propertyConfigParcel.getDataType()
+ "; contents: "
+ propertyConfigParcel);
}
}
}
/** Configuration for a property of type String in a Document. */
public static final class StringPropertyConfig extends PropertyConfig {
/**
* Encapsulates the configurations on how AppSearch should query/index these terms.
*
* @hide
*/
@IntDef(
value = {
INDEXING_TYPE_NONE,
INDEXING_TYPE_EXACT_TERMS,
INDEXING_TYPE_PREFIXES,
})
@Retention(RetentionPolicy.SOURCE)
public @interface IndexingType {}
/** Content in this property will not be tokenized or indexed. */
public static final int INDEXING_TYPE_NONE = 0;
/**
* Content in this property should only be returned for queries matching the exact tokens
* appearing in this property.
*
* For example, a property with "fool" should NOT match a query for "foo".
*/
public static final int INDEXING_TYPE_EXACT_TERMS = 1;
/**
* Content in this property should be returned for queries that are either exact matches or
* query matches of the tokens appearing in this property.
*
* For example, a property with "fool" should match a query for "foo".
*/
public static final int INDEXING_TYPE_PREFIXES = 2;
/**
* Configures how tokens should be extracted from this property.
*
* NOTE: The integer values of these constants must match the proto enum constants in
* com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
*
* @hide
*/
@IntDef(
value = {
TOKENIZER_TYPE_NONE,
TOKENIZER_TYPE_PLAIN,
TOKENIZER_TYPE_VERBATIM,
TOKENIZER_TYPE_RFC822
})
@Retention(RetentionPolicy.SOURCE)
public @interface TokenizerType {}
/**
* This value indicates that no tokens should be extracted from this property.
*
* It is only valid for tokenizer_type to be 'NONE' if {@link #getIndexingType} is {@link
* #INDEXING_TYPE_NONE}.
*/
public static final int TOKENIZER_TYPE_NONE = 0;
/**
* Tokenization for plain text. This value indicates that tokens should be extracted from
* this property based on word breaks. Segments of whitespace and punctuation are not
* considered tokens.
*
* For example, a property with "foo bar. baz." will produce tokens for "foo", "bar" and
* "baz". The segments " " and "." will not be considered tokens.
*
* It is only valid for tokenizer_type to be 'PLAIN' if {@link #getIndexingType} is
* {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}.
*/
public static final int TOKENIZER_TYPE_PLAIN = 1;
/**
* This value indicates that no normalization or segmentation should be applied to string
* values that are tokenized using this type. Therefore, the output token is equivalent to
* the raw string value.
*
* For example, a property with "Hello, world!" will produce the token "Hello, world!",
* preserving punctuation and capitalization, and not creating separate tokens between the
* space.
*
* It is only valid for tokenizer_type to be 'VERBATIM' if {@link #getIndexingType} is
* {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}.
*/
public static final int TOKENIZER_TYPE_VERBATIM = 2;
/**
* Tokenization for emails. This value indicates that tokens should be extracted from this
* property based on email structure.
*
* For example, a property with "alex.sav@google.com" will produce tokens for "alex",
* "sav", "alex.sav", "google", "com", and "alexsav@google.com"
*
* It is only valid for tokenizer_type to be 'RFC822' if {@link #getIndexingType} is
* {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}.
*/
public static final int TOKENIZER_TYPE_RFC822 = 3;
/**
* The joinable value type of the property. By setting the appropriate joinable value type
* for a property, the client can use the property for joining documents from other schema
* types using Search API (see {@link JoinSpec}).
*
* @hide
*/
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.JoinableConfig.ValueType.Code.
@IntDef(
value = {
JOINABLE_VALUE_TYPE_NONE,
JOINABLE_VALUE_TYPE_QUALIFIED_ID,
})
@Retention(RetentionPolicy.SOURCE)
public @interface JoinableValueType {}
/** Content in this property is not joinable. */
public static final int JOINABLE_VALUE_TYPE_NONE = 0;
/**
* Content in this string property will be used as a qualified id to join documents.
*
* For more details about the description field, see {@link
* AppSearchSchema.PropertyConfig#getDescription}.
*/
@FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS)
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public StringPropertyConfig.Builder setDescription(@NonNull String description) {
mDescription = Objects.requireNonNull(description);
return this;
}
/**
* Sets the cardinality of the property (whether it is optional, required or repeated).
*
* If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@CanIgnoreReturnValue
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public StringPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/**
* Configures how a property should be indexed so that it can be retrieved by queries.
*
* If this method is not called, the default indexing type is {@link
* StringPropertyConfig#INDEXING_TYPE_NONE}, so that it cannot be matched by queries.
*/
@CanIgnoreReturnValue
@NonNull
public StringPropertyConfig.Builder setIndexingType(@IndexingType int indexingType) {
Preconditions.checkArgumentInRange(
indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_PREFIXES, "indexingType");
mIndexingType = indexingType;
return this;
}
/**
* Configures how this property should be tokenized (split into words).
*
* If this method is not called, the default indexing type is {@link
* StringPropertyConfig#TOKENIZER_TYPE_NONE}, so that it is not tokenized.
*
* This method must be called with a value other than {@link
* StringPropertyConfig#TOKENIZER_TYPE_NONE} if the property is indexed (that is, if
* {@link #setIndexingType} has been called with a value other than {@link
* StringPropertyConfig#INDEXING_TYPE_NONE}).
*/
@CanIgnoreReturnValue
@NonNull
public StringPropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
Preconditions.checkArgumentInRange(
tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_RFC822, "tokenizerType");
mTokenizerType = tokenizerType;
return this;
}
/**
* Configures how this property should be used as a joining matcher.
*
* If this method is not called, the default joinable value type is {@link
* StringPropertyConfig#JOINABLE_VALUE_TYPE_NONE}, so that it is not joinable.
*
* At most, 64 properties can be set as joinable per schema.
*/
@CanIgnoreReturnValue
@NonNull
public StringPropertyConfig.Builder setJoinableValueType(
@JoinableValueType int joinableValueType) {
Preconditions.checkArgumentInRange(
joinableValueType,
JOINABLE_VALUE_TYPE_NONE,
JOINABLE_VALUE_TYPE_QUALIFIED_ID,
"joinableValueType");
mJoinableValueType = joinableValueType;
return this;
}
/** Constructs a new {@link StringPropertyConfig} from the contents of this builder. */
@NonNull
public StringPropertyConfig build() {
if (mTokenizerType == TOKENIZER_TYPE_NONE) {
Preconditions.checkState(
mIndexingType == INDEXING_TYPE_NONE,
"Cannot set "
+ "TOKENIZER_TYPE_NONE with an indexing type other than "
+ "INDEXING_TYPE_NONE.");
} else {
Preconditions.checkState(
mIndexingType != INDEXING_TYPE_NONE,
"Cannot set " + "TOKENIZER_TYPE_PLAIN with INDEXING_TYPE_NONE.");
}
if (mJoinableValueType == JOINABLE_VALUE_TYPE_QUALIFIED_ID) {
Preconditions.checkState(
mCardinality != CARDINALITY_REPEATED,
"Cannot set JOINABLE_VALUE_TYPE_QUALIFIED_ID with"
+ " CARDINALITY_REPEATED.");
} else {
Preconditions.checkState(
!mDeletionPropagation,
"Cannot set deletion "
+ "propagation without setting a joinable value type");
}
PropertyConfigParcel.StringIndexingConfigParcel stringConfigParcel =
new StringIndexingConfigParcel(mIndexingType, mTokenizerType);
JoinableConfigParcel joinableConfigParcel =
new JoinableConfigParcel(mJoinableValueType, mDeletionPropagation);
return new StringPropertyConfig(
PropertyConfigParcel.createForString(
mPropertyName,
mDescription,
mCardinality,
stringConfigParcel,
joinableConfigParcel));
}
}
/**
* Appends a debug string for the {@link StringPropertyConfig} instance to the given string
* builder.
*
* This appends fields specific to a {@link StringPropertyConfig} instance.
*
* @param builder the builder to append to.
*/
void appendStringPropertyConfigFields(@NonNull IndentingStringBuilder builder) {
switch (getIndexingType()) {
case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE:
builder.append("indexingType: INDEXING_TYPE_NONE,\n");
break;
case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS:
builder.append("indexingType: INDEXING_TYPE_EXACT_TERMS,\n");
break;
case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES:
builder.append("indexingType: INDEXING_TYPE_PREFIXES,\n");
break;
default:
builder.append("indexingType: INDEXING_TYPE_UNKNOWN,\n");
}
switch (getTokenizerType()) {
case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE:
builder.append("tokenizerType: TOKENIZER_TYPE_NONE,\n");
break;
case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN:
builder.append("tokenizerType: TOKENIZER_TYPE_PLAIN,\n");
break;
case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_VERBATIM:
builder.append("tokenizerType: TOKENIZER_TYPE_VERBATIM,\n");
break;
case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_RFC822:
builder.append("tokenizerType: TOKENIZER_TYPE_RFC822,\n");
break;
default:
builder.append("tokenizerType: TOKENIZER_TYPE_UNKNOWN,\n");
}
switch (getJoinableValueType()) {
case AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE:
builder.append("joinableValueType: JOINABLE_VALUE_TYPE_NONE,\n");
break;
case AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_QUALIFIED_ID:
builder.append("joinableValueType: JOINABLE_VALUE_TYPE_QUALIFIED_ID,\n");
break;
default:
builder.append("joinableValueType: JOINABLE_VALUE_TYPE_UNKNOWN,\n");
}
}
}
/** Configuration for a property containing a 64-bit integer. */
public static final class LongPropertyConfig extends PropertyConfig {
/**
* Encapsulates the configurations on how AppSearch should query/index these 64-bit
* integers.
*
* @hide
*/
@IntDef(value = {INDEXING_TYPE_NONE, INDEXING_TYPE_RANGE})
@Retention(RetentionPolicy.SOURCE)
public @interface IndexingType {}
/** Content in this property will not be indexed. */
public static final int INDEXING_TYPE_NONE = 0;
/**
* Content in this property will be indexed and can be fetched via numeric search range
* query.
*
* For example, a property with 1024 should match numeric search range query [0, 2000].
*/
public static final int INDEXING_TYPE_RANGE = 1;
LongPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
super(propertyConfigParcel);
}
/** Returns how the property is indexed. */
@IndexingType
public int getIndexingType() {
PropertyConfigParcel.IntegerIndexingConfigParcel indexingConfigParcel =
mPropertyConfigParcel.getIntegerIndexingConfigParcel();
if (indexingConfigParcel == null) {
return INDEXING_TYPE_NONE;
}
return indexingConfigParcel.getIndexingType();
}
/** Builder for {@link LongPropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private String mDescription = "";
@Cardinality private int mCardinality = CARDINALITY_OPTIONAL;
@IndexingType private int mIndexingType = INDEXING_TYPE_NONE;
/** Creates a new {@link LongPropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
}
/**
* Sets a natural language description of this property.
*
* For more details about the description field, see {@link
* AppSearchSchema.PropertyConfig#getDescription}.
*/
@FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS)
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public LongPropertyConfig.Builder setDescription(@NonNull String description) {
mDescription = Objects.requireNonNull(description);
return this;
}
/**
* Sets the cardinality of the property (whether it is optional, required or repeated).
*
* If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@CanIgnoreReturnValue
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public LongPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/**
* Configures how a property should be indexed so that it can be retrieved by queries.
*
* If this method is not called, the default indexing type is {@link
* LongPropertyConfig#INDEXING_TYPE_NONE}, so that it will not be indexed and cannot be
* matched by queries.
*/
@CanIgnoreReturnValue
@NonNull
public LongPropertyConfig.Builder setIndexingType(@IndexingType int indexingType) {
Preconditions.checkArgumentInRange(
indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_RANGE, "indexingType");
mIndexingType = indexingType;
return this;
}
/** Constructs a new {@link LongPropertyConfig} from the contents of this builder. */
@NonNull
public LongPropertyConfig build() {
return new LongPropertyConfig(
PropertyConfigParcel.createForLong(
mPropertyName, mDescription, mCardinality, mIndexingType));
}
}
/**
* Appends a debug string for the {@link LongPropertyConfig} instance to the given string
* builder.
*
* This appends fields specific to a {@link LongPropertyConfig} instance.
*
* @param builder the builder to append to.
*/
void appendLongPropertyConfigFields(@NonNull IndentingStringBuilder builder) {
switch (getIndexingType()) {
case AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_NONE:
builder.append("indexingType: INDEXING_TYPE_NONE,\n");
break;
case AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE:
builder.append("indexingType: INDEXING_TYPE_RANGE,\n");
break;
default:
builder.append("indexingType: INDEXING_TYPE_UNKNOWN,\n");
}
}
}
/** Configuration for a property containing a double-precision decimal number. */
public static final class DoublePropertyConfig extends PropertyConfig {
DoublePropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
super(propertyConfigParcel);
}
/** Builder for {@link DoublePropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private String mDescription = "";
@Cardinality private int mCardinality = CARDINALITY_OPTIONAL;
/** Creates a new {@link DoublePropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
}
/**
* Sets a natural language description of this property.
*
* For more details about the description field, see {@link
* AppSearchSchema.PropertyConfig#getDescription}.
*/
@FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS)
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public DoublePropertyConfig.Builder setDescription(@NonNull String description) {
mDescription = Objects.requireNonNull(description);
return this;
}
/**
* Sets the cardinality of the property (whether it is optional, required or repeated).
*
* If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@CanIgnoreReturnValue
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public DoublePropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/** Constructs a new {@link DoublePropertyConfig} from the contents of this builder. */
@NonNull
public DoublePropertyConfig build() {
return new DoublePropertyConfig(
PropertyConfigParcel.createForDouble(
mPropertyName, mDescription, mCardinality));
}
}
}
/** Configuration for a property containing a boolean. */
public static final class BooleanPropertyConfig extends PropertyConfig {
BooleanPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
super(propertyConfigParcel);
}
/** Builder for {@link BooleanPropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private String mDescription = "";
@Cardinality private int mCardinality = CARDINALITY_OPTIONAL;
/** Creates a new {@link BooleanPropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
}
/**
* Sets a natural language description of this property.
*
* For more details about the description field, see {@link
* AppSearchSchema.PropertyConfig#getDescription}.
*/
@FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS)
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public BooleanPropertyConfig.Builder setDescription(@NonNull String description) {
mDescription = Objects.requireNonNull(description);
return this;
}
/**
* Sets the cardinality of the property (whether it is optional, required or repeated).
*
* If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@CanIgnoreReturnValue
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public BooleanPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/** Constructs a new {@link BooleanPropertyConfig} from the contents of this builder. */
@NonNull
public BooleanPropertyConfig build() {
return new BooleanPropertyConfig(
PropertyConfigParcel.createForBoolean(
mPropertyName, mDescription, mCardinality));
}
}
}
/** Configuration for a property containing a byte array. */
public static final class BytesPropertyConfig extends PropertyConfig {
BytesPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
super(propertyConfigParcel);
}
/** Builder for {@link BytesPropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private String mDescription = "";
@Cardinality private int mCardinality = CARDINALITY_OPTIONAL;
/** Creates a new {@link BytesPropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
}
/**
* Sets a natural language description of this property.
*
* For more details about the description field, see {@link
* AppSearchSchema.PropertyConfig#getDescription}.
*/
@FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS)
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public BytesPropertyConfig.Builder setDescription(@NonNull String description) {
mDescription = Objects.requireNonNull(description);
return this;
}
/**
* Sets the cardinality of the property (whether it is optional, required or repeated).
*
* If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@CanIgnoreReturnValue
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public BytesPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/** Constructs a new {@link BytesPropertyConfig} from the contents of this builder. */
@NonNull
public BytesPropertyConfig build() {
return new BytesPropertyConfig(
PropertyConfigParcel.createForBytes(
mPropertyName, mDescription, mCardinality));
}
}
}
/** Configuration for a property containing another Document. */
public static final class DocumentPropertyConfig extends PropertyConfig {
DocumentPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
super(propertyConfigParcel);
}
/** Returns the logical schema-type of the contents of this document property. */
@NonNull
public String getSchemaType() {
return Objects.requireNonNull(mPropertyConfigParcel.getSchemaType());
}
/**
* Returns whether properties in the nested document should be indexed according to that
* document's schema.
*
* If false, the nested document's properties are not indexed regardless of its own
* schema.
*
* @see DocumentPropertyConfig.Builder#addIndexableNestedProperties(Collection) for indexing
* a subset of properties from the nested document.
*/
public boolean shouldIndexNestedProperties() {
DocumentIndexingConfigParcel indexingConfigParcel =
mPropertyConfigParcel.getDocumentIndexingConfigParcel();
if (indexingConfigParcel == null) {
return false;
}
return indexingConfigParcel.shouldIndexNestedProperties();
}
/** Returns the list of indexable nested properties for the nested document. */
@FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES)
@NonNull
public List For more details about the description field, see {@link
* AppSearchSchema.PropertyConfig#getDescription}.
*/
@FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS)
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public DocumentPropertyConfig.Builder setDescription(@NonNull String description) {
mDescription = Objects.requireNonNull(description);
return this;
}
/**
* Sets the cardinality of the property (whether it is optional, required or repeated).
*
* If this method is not called, the default cardinality is {@link
* PropertyConfig#CARDINALITY_OPTIONAL}.
*/
@CanIgnoreReturnValue
@SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass
@NonNull
public DocumentPropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
Preconditions.checkArgumentInRange(
cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
mCardinality = cardinality;
return this;
}
/**
* Configures whether properties in the nested document should be indexed according to
* that document's schema.
*
* If false, the nested document's properties are not indexed regardless of its own
* schema.
*
* To index a subset of properties from the nested document, set this to false and
* use {@link #addIndexableNestedProperties(Collection)}.
*/
@CanIgnoreReturnValue
@NonNull
public DocumentPropertyConfig.Builder setShouldIndexNestedProperties(
boolean indexNestedProperties) {
mShouldIndexNestedProperties = indexNestedProperties;
return this;
}
/**
* Adds one or more properties for indexing from the nested document property.
*
* @see #addIndexableNestedProperties(Collection)
*/
@FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES)
@CanIgnoreReturnValue
@NonNull
public DocumentPropertyConfig.Builder addIndexableNestedProperties(
@NonNull String... indexableNestedProperties) {
Objects.requireNonNull(indexableNestedProperties);
return addIndexableNestedProperties(Arrays.asList(indexableNestedProperties));
}
/**
* Adds one or more property paths for indexing from the nested document property.
*
* @see #addIndexableNestedProperties(Collection)
*/
@FlaggedApi(Flags.FLAG_ENABLE_GET_PARENT_TYPES_AND_INDEXABLE_NESTED_PROPERTIES)
@CanIgnoreReturnValue
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public DocumentPropertyConfig.Builder addIndexableNestedPropertyPaths(
@NonNull PropertyPath... indexableNestedPropertyPaths) {
Objects.requireNonNull(indexableNestedPropertyPaths);
return addIndexableNestedPropertyPaths(Arrays.asList(indexableNestedPropertyPaths));
}
/**
* Adds one or more properties for indexing from the nested document. The added property
* will be indexed according to that property's indexing configurations in the
* document's schema definition. All properties in this list will consume a sectionId
* regardless of its actual indexing config -- this includes properties added that do
* not actually exist, as well as properties that are not set as indexable in the nested
* schema type.
*
* Input strings should follow the format of the property path for the nested
* property, with '.' as the path separator. This nested document's property name should
* not be included in the property path.
*
* Ex. Consider an 'Organization' schema type which defines a nested document
* property 'address' (Address schema type), where Address has a nested document
* property 'country' (Country schema type with string 'name' property), and a string
* 'street' property. The 'street' and 'country's name' properties from the 'address'
* document property can be indexed for the 'Organization' schema type by calling:
*
* {@link DocumentPropertyConfig.Builder#setShouldIndexNestedProperties} is required
* to be false if any indexable nested property is added this way for the document
* property. Attempting to build a DocumentPropertyConfig when this is not true throws
* {@link IllegalArgumentException}.
*/
@CanIgnoreReturnValue
@NonNull
public DocumentPropertyConfig.Builder addIndexableNestedProperties(
@NonNull Collection This appends fields specific to a {@link DocumentPropertyConfig} instance.
*
* @param builder the builder to append to.
*/
void appendDocumentPropertyConfigFields(@NonNull IndentingStringBuilder builder) {
builder.append("shouldIndexNestedProperties: ")
.append(shouldIndexNestedProperties())
.append(",\n");
builder.append("indexableNestedProperties: ")
.append(getIndexableNestedProperties())
.append(",\n");
builder.append("schemaType: \"").append(getSchemaType()).append("\",\n");
}
}
}
*
*
*
*
*
*
*
*/
public static final int JOINABLE_VALUE_TYPE_QUALIFIED_ID = 1;
StringPropertyConfig(@NonNull PropertyConfigParcel propertyConfigParcel) {
super(propertyConfigParcel);
}
/** Returns how the property is indexed. */
@IndexingType
public int getIndexingType() {
StringIndexingConfigParcel indexingConfigParcel =
mPropertyConfigParcel.getStringIndexingConfigParcel();
if (indexingConfigParcel == null) {
return INDEXING_TYPE_NONE;
}
return indexingConfigParcel.getIndexingType();
}
/** Returns how this property is tokenized (split into words). */
@TokenizerType
public int getTokenizerType() {
StringIndexingConfigParcel indexingConfigParcel =
mPropertyConfigParcel.getStringIndexingConfigParcel();
if (indexingConfigParcel == null) {
return TOKENIZER_TYPE_NONE;
}
return indexingConfigParcel.getTokenizerType();
}
/**
* Returns how this property is going to be used to join documents from other schema types.
*/
@JoinableValueType
public int getJoinableValueType() {
JoinableConfigParcel joinableConfigParcel =
mPropertyConfigParcel.getJoinableConfigParcel();
if (joinableConfigParcel == null) {
return JOINABLE_VALUE_TYPE_NONE;
}
return joinableConfigParcel.getJoinableValueType();
}
/** Builder for {@link StringPropertyConfig}. */
public static final class Builder {
private final String mPropertyName;
private String mDescription = "";
@Cardinality private int mCardinality = CARDINALITY_OPTIONAL;
@IndexingType private int mIndexingType = INDEXING_TYPE_NONE;
@TokenizerType private int mTokenizerType = TOKENIZER_TYPE_NONE;
@JoinableValueType private int mJoinableValueType = JOINABLE_VALUE_TYPE_NONE;
private boolean mDeletionPropagation = false;
/** Creates a new {@link StringPropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
mPropertyName = Objects.requireNonNull(propertyName);
}
/**
* Sets a natural language description of this property.
*
* {@code
* OrganizationSchema.addProperty(
* new DocumentPropertyConfig.Builder("address", "Address")
* .addIndexableNestedProperties("street", "country.name")
* .build()).
* }
*
*