diff options
9 files changed, 1080 insertions, 899 deletions
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java deleted file mode 100644 index 8bf13eec6249..000000000000 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java +++ /dev/null @@ -1,854 +0,0 @@ -/* - * Copyright (C) 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.CurrentTimeMillisLong; -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.appsearch.AppSearchSchema.PropertyConfig; -import android.os.Bundle; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; - -import com.google.android.icing.proto.DocumentProto; -import com.google.android.icing.proto.PropertyProto; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Objects; - -/** - * Collection of all AppSearch Document Types. - * - * @hide - */ -// TODO(b/143789408) Spilt this class to make all subclasses to their own file. -public final class AppSearch { - - private AppSearch() {} - /** - * Represents a document unit. - * - * <p>Documents are constructed via {@link Document.Builder}. - * - * @hide - */ - // TODO(b/143789408) set TTL for document in mProtoBuilder - // TODO(b/144518768) add visibility field if the stakeholders are comfortable with a no-op - // opt-in for this release. - public static class Document { - private static final String TAG = "AppSearch.Document"; - - /** - * The maximum number of elements in a repeatable field. Will reject the request if exceed - * this limit. - */ - private static final int MAX_REPEATED_PROPERTY_LENGTH = 100; - - /** - * The maximum {@link String#length} of a {@link String} field. Will reject the request if - * {@link String}s longer than this. - */ - private static final int MAX_STRING_LENGTH = 20_000; - - /** - * Contains {@link Document} basic information (uri, schemaType etc) and properties ordered - * by keys. - */ - @NonNull - private final DocumentProto mProto; - - /** Contains all properties in {@link #mProto} to support get properties via keys. */ - @NonNull - private final Bundle mPropertyBundle; - - /** - * Create a new {@link Document}. - * @param proto Contains {@link Document} basic information (uri, schemaType etc) and - * properties ordered by keys. - * @param propertyBundle Contains all properties in {@link #mProto} to support get - * properties via keys. - */ - private Document(@NonNull DocumentProto proto, @NonNull Bundle propertyBundle) { - this.mProto = proto; - this.mPropertyBundle = propertyBundle; - } - - /** - * Create a new {@link Document} from an existing instance. - * - * <p>This method should be only used by constructor of a subclass. - */ - // TODO(b/143789408) add constructor take DocumentProto to create a document. - protected Document(@NonNull Document document) { - this(document.mProto, document.mPropertyBundle); - } - - /** @hide */ - Document(@NonNull DocumentProto documentProto) { - this(documentProto, new Bundle()); - for (int i = 0; i < documentProto.getPropertiesCount(); i++) { - PropertyProto property = documentProto.getProperties(i); - String name = property.getName(); - if (property.getStringValuesCount() > 0) { - String[] values = new String[property.getStringValuesCount()]; - for (int j = 0; j < values.length; j++) { - values[j] = property.getStringValues(j); - } - mPropertyBundle.putStringArray(name, values); - } else if (property.getInt64ValuesCount() > 0) { - long[] values = new long[property.getInt64ValuesCount()]; - for (int j = 0; j < values.length; j++) { - values[j] = property.getInt64Values(j); - } - mPropertyBundle.putLongArray(property.getName(), values); - } else if (property.getDoubleValuesCount() > 0) { - double[] values = new double[property.getDoubleValuesCount()]; - for (int j = 0; j < values.length; j++) { - values[j] = property.getDoubleValues(j); - } - mPropertyBundle.putDoubleArray(property.getName(), values); - } else if (property.getBooleanValuesCount() > 0) { - boolean[] values = new boolean[property.getBooleanValuesCount()]; - for (int j = 0; j < values.length; j++) { - values[j] = property.getBooleanValues(j); - } - mPropertyBundle.putBooleanArray(property.getName(), values); - } else if (property.getBytesValuesCount() > 0) { - byte[][] values = new byte[property.getBytesValuesCount()][]; - for (int j = 0; j < values.length; j++) { - values[j] = property.getBytesValues(j).toByteArray(); - } - mPropertyBundle.putObject(name, values); - } else if (property.getDocumentValuesCount() > 0) { - Document[] values = new Document[property.getDocumentValuesCount()]; - for (int j = 0; j < values.length; j++) { - values[j] = new Document(property.getDocumentValues(j)); - } - mPropertyBundle.putObject(name, values); - } else { - throw new IllegalStateException("Unknown type of value: " + name); - } - } - } - - /** - * Creates a new {@link Document.Builder}. - * - * @param uri The uri of {@link Document}. - * @param schemaType The schema type of the {@link Document}. The passed-in - * {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior to - * inserting a document of this {@code schemaType} into the AppSearch index using - * {@link AppSearchManager#put}. Otherwise, the document will be rejected by - * {@link AppSearchManager#put}. - * @hide - */ - @NonNull - public static Builder newBuilder(@NonNull String uri, @NonNull String schemaType) { - return new Builder(uri, schemaType); - } - - /** - * Get the {@link DocumentProto} of the {@link Document}. - * - * <p>The {@link DocumentProto} contains {@link Document}'s basic information and all - * properties ordered by keys. - * @hide - */ - @NonNull - @VisibleForTesting - public DocumentProto getProto() { - return mProto; - } - - /** - * Get the uri of the {@link Document}. - * - * @hide - */ - @NonNull - public String getUri() { - return mProto.getUri(); - } - - /** - * Get the schema type of the {@link Document}. - * @hide - */ - @NonNull - public String getSchemaType() { - return mProto.getSchema(); - } - - /** - * Get the creation timestamp in milliseconds of the {@link Document}. Value will be in the - * {@link System#currentTimeMillis()} time base. - * - * @hide - */ - @CurrentTimeMillisLong - public long getCreationTimestampMillis() { - return mProto.getCreationTimestampMs(); - } - - /** - * Returns the score of the {@link Document}. - * - * <p>The score is a query-independent measure of the document's quality, relative to other - * {@link Document}s of the same type. - * - * <p>The default value is 0. - * - * @hide - */ - public int getScore() { - return mProto.getScore(); - } - - /** - * Retrieve a {@link String} value by key. - * - * @param key The key to look for. - * @return The first {@link String} associated with the given key or {@code null} if there - * is no such key or the value is of a different type. - * @hide - */ - @Nullable - public String getPropertyString(@NonNull String key) { - String[] propertyArray = getPropertyStringArray(key); - if (ArrayUtils.isEmpty(propertyArray)) { - return null; - } - warnIfSinglePropertyTooLong("String", key, propertyArray.length); - return propertyArray[0]; - } - - /** - * Retrieve a {@link Long} value by key. - * - * @param key The key to look for. - * @return The first {@link Long} associated with the given key or {@code null} if there - * is no such key or the value is of a different type. - * @hide - */ - @Nullable - public Long getPropertyLong(@NonNull String key) { - long[] propertyArray = getPropertyLongArray(key); - if (ArrayUtils.isEmpty(propertyArray)) { - return null; - } - warnIfSinglePropertyTooLong("Long", key, propertyArray.length); - return propertyArray[0]; - } - - /** - * Retrieve a {@link Double} value by key. - * - * @param key The key to look for. - * @return The first {@link Double} associated with the given key or {@code null} if there - * is no such key or the value is of a different type. - * @hide - */ - @Nullable - public Double getPropertyDouble(@NonNull String key) { - double[] propertyArray = getPropertyDoubleArray(key); - // TODO(tytytyww): Add support double array to ArraysUtils.isEmpty(). - if (propertyArray == null || propertyArray.length == 0) { - return null; - } - warnIfSinglePropertyTooLong("Double", key, propertyArray.length); - return propertyArray[0]; - } - - /** - * Retrieve a {@link Boolean} value by key. - * - * @param key The key to look for. - * @return The first {@link Boolean} associated with the given key or {@code null} if there - * is no such key or the value is of a different type. - * @hide - */ - @Nullable - public Boolean getPropertyBoolean(@NonNull String key) { - boolean[] propertyArray = getPropertyBooleanArray(key); - if (ArrayUtils.isEmpty(propertyArray)) { - return null; - } - warnIfSinglePropertyTooLong("Boolean", key, propertyArray.length); - return propertyArray[0]; - } - - /** Prints a warning to logcat if the given propertyLength is greater than 1. */ - private static void warnIfSinglePropertyTooLong( - @NonNull String propertyType, @NonNull String key, int propertyLength) { - if (propertyLength > 1) { - Log.w(TAG, "The value for \"" + key + "\" contains " + propertyLength - + " elements. Only the first one will be returned from " - + "getProperty" + propertyType + "(). Try getProperty" + propertyType - + "Array()."); - } - } - - /** - * Retrieve a repeated {@code String} property by key. - * - * @param key The key to look for. - * @return The {@code String[]} associated with the given key, or {@code null} if no value - * is set or the value is of a different type. - * @hide - */ - @Nullable - public String[] getPropertyStringArray(@NonNull String key) { - return getAndCastPropertyArray(key, String[].class); - } - - /** - * Retrieve a repeated {@code long} property by key. - * - * @param key The key to look for. - * @return The {@code long[]} associated with the given key, or {@code null} if no value is - * set or the value is of a different type. - * @hide - */ - @Nullable - public long[] getPropertyLongArray(@NonNull String key) { - return getAndCastPropertyArray(key, long[].class); - } - - /** - * Retrieve a repeated {@code double} property by key. - * - * @param key The key to look for. - * @return The {@code double[]} associated with the given key, or {@code null} if no value - * is set or the value is of a different type. - * @hide - */ - @Nullable - public double[] getPropertyDoubleArray(@NonNull String key) { - return getAndCastPropertyArray(key, double[].class); - } - - /** - * Retrieve a repeated {@code boolean} property by key. - * - * @param key The key to look for. - * @return The {@code boolean[]} associated with the given key, or {@code null} if no value - * is set or the value is of a different type. - * @hide - */ - @Nullable - public boolean[] getPropertyBooleanArray(@NonNull String key) { - return getAndCastPropertyArray(key, boolean[].class); - } - - /** - * Gets a repeated property of the given key, and casts it to the given class type, which - * must be an array class type. - */ - @Nullable - private <T> T getAndCastPropertyArray(@NonNull String key, @NonNull Class<T> tClass) { - Object value = mPropertyBundle.get(key); - if (value == null) { - return null; - } - try { - return tClass.cast(value); - } catch (ClassCastException e) { - Log.w(TAG, "Error casting to requested type for key \"" + key + "\"", e); - return null; - } - } - - @Override - public boolean equals(@Nullable Object other) { - // Check only proto's equality is sufficient here since all properties in - // mPropertyBundle are ordered by keys and stored in proto. - if (this == other) { - return true; - } - if (!(other instanceof Document)) { - return false; - } - Document otherDocument = (Document) other; - return this.mProto.equals(otherDocument.mProto); - } - - @Override - public int hashCode() { - // Hash only proto is sufficient here since all properties in mPropertyBundle are - // ordered by keys and stored in proto. - return mProto.hashCode(); - } - - @Override - public String toString() { - return mProto.toString(); - } - - /** - * The builder class for {@link Document}. - * - * @param <BuilderType> Type of subclass who extend this. - * @hide - */ - public static class Builder<BuilderType extends Builder> { - - private final Bundle mPropertyBundle = new Bundle(); - private final DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder(); - private final BuilderType mBuilderTypeInstance; - - /** - * Create a new {@link Document.Builder}. - * - * @param uri The uri of {@link Document}. - * @param schemaType The schema type of the {@link Document}. The passed-in - * {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior - * to inserting a document of this {@code schemaType} into the AppSearch index using - * {@link AppSearchManager#put}. Otherwise, the document will be rejected by - * {@link AppSearchManager#put}. - * @hide - */ - protected Builder(@NonNull String uri, @NonNull String schemaType) { - mBuilderTypeInstance = (BuilderType) this; - mProtoBuilder.setUri(uri).setSchema(schemaType); - // Set current timestamp for creation timestamp by default. - setCreationTimestampMillis(System.currentTimeMillis()); - } - - /** - * Set the score of the {@link Document}. - * - * <p>The score is a query-independent measure of the document's quality, relative to - * other {@link Document}s of the same type. - * - * @throws IllegalArgumentException If the provided value is negative. - * @hide - */ - @NonNull - public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) { - if (score < 0) { - throw new IllegalArgumentException("Document score cannot be negative"); - } - mProtoBuilder.setScore(score); - return mBuilderTypeInstance; - } - - /** - * Set the creation timestamp in milliseconds of the {@link Document}. Should be set - * using a value obtained from the {@link System#currentTimeMillis()} time base. - * - * @hide - */ - @NonNull - public BuilderType setCreationTimestampMillis( - @CurrentTimeMillisLong long creationTimestampMillis) { - mProtoBuilder.setCreationTimestampMs(creationTimestampMillis); - return mBuilderTypeInstance; - } - - /** - * Sets one or multiple {@code String} values for a property, replacing its previous - * values. - * - * @param key The key associated with the {@code values}. - * @param values The {@code String} values of the property. - * @hide - */ - @NonNull - public BuilderType setProperty(@NonNull String key, @NonNull String... values) { - putInBundle(mPropertyBundle, key, values); - return mBuilderTypeInstance; - } - - /** - * Sets one or multiple {@code boolean} values for a property, replacing its previous - * values. - * - * @param key The key associated with the {@code values}. - * @param values The {@code boolean} values of the schema.org property. - * @hide - */ - @NonNull - public BuilderType setProperty(@NonNull String key, @NonNull boolean... values) { - putInBundle(mPropertyBundle, key, values); - return mBuilderTypeInstance; - } - - /** - * Sets one or multiple {@code long} values for a property, replacing its previous - * values. - * - * @param key The key associated with the {@code values}. - * @param values The {@code long} values of the schema.org property. - * @hide - */ - @NonNull - public BuilderType setProperty(@NonNull String key, @NonNull long... values) { - putInBundle(mPropertyBundle, key, values); - return mBuilderTypeInstance; - } - - /** - * Sets one or multiple {@code double} values for a property, replacing its previous - * values. - * - * @param key The key associated with the {@code values}. - * @param values The {@code double} values of the schema.org property. - * @hide - */ - @NonNull - public BuilderType setProperty(@NonNull String key, @NonNull double... values) { - putInBundle(mPropertyBundle, key, values); - return mBuilderTypeInstance; - } - - private static void putInBundle( - @NonNull Bundle bundle, @NonNull String key, @NonNull String... values) - throws IllegalArgumentException { - Objects.requireNonNull(key); - Objects.requireNonNull(values); - validateRepeatedPropertyLength(key, values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] == null) { - throw new IllegalArgumentException("The String at " + i + " is null."); - } else if (values[i].length() > MAX_STRING_LENGTH) { - throw new IllegalArgumentException("The String at " + i + " length is: " - + values[i].length() + ", which exceeds length limit: " - + MAX_STRING_LENGTH + "."); - } - } - bundle.putStringArray(key, values); - } - - private static void putInBundle( - @NonNull Bundle bundle, @NonNull String key, @NonNull boolean... values) { - Objects.requireNonNull(key); - Objects.requireNonNull(values); - validateRepeatedPropertyLength(key, values.length); - bundle.putBooleanArray(key, values); - } - - private static void putInBundle( - @NonNull Bundle bundle, @NonNull String key, @NonNull double... values) { - Objects.requireNonNull(key); - Objects.requireNonNull(values); - validateRepeatedPropertyLength(key, values.length); - bundle.putDoubleArray(key, values); - } - - private static void putInBundle( - @NonNull Bundle bundle, @NonNull String key, @NonNull long... values) { - Objects.requireNonNull(key); - Objects.requireNonNull(values); - validateRepeatedPropertyLength(key, values.length); - bundle.putLongArray(key, values); - } - - private static void validateRepeatedPropertyLength(@NonNull String key, int length) { - if (length == 0) { - throw new IllegalArgumentException("The input array is empty."); - } else if (length > MAX_REPEATED_PROPERTY_LENGTH) { - throw new IllegalArgumentException( - "Repeated property \"" + key + "\" has length " + length - + ", which exceeds the limit of " - + MAX_REPEATED_PROPERTY_LENGTH); - } - } - - /** - * Builds the {@link Document} object. - * @hide - */ - public Document build() { - // Build proto by sorting the keys in propertyBundle to exclude the influence of - // order. Therefore documents will generate same proto as long as the contents are - // same. Note that the order of repeated fields is still preserved. - ArrayList<String> keys = new ArrayList<>(mPropertyBundle.keySet()); - Collections.sort(keys); - for (String key : keys) { - Object values = mPropertyBundle.get(key); - PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(key); - if (values instanceof boolean[]) { - for (boolean value : (boolean[]) values) { - propertyProto.addBooleanValues(value); - } - } else if (values instanceof long[]) { - for (long value : (long[]) values) { - propertyProto.addInt64Values(value); - } - } else if (values instanceof double[]) { - for (double value : (double[]) values) { - propertyProto.addDoubleValues(value); - } - } else if (values instanceof String[]) { - for (String value : (String[]) values) { - propertyProto.addStringValues(value); - } - } else { - throw new IllegalStateException( - "Property \"" + key + "\" has unsupported value type \"" - + values.getClass().getSimpleName() + "\""); - } - mProtoBuilder.addProperties(propertyProto); - } - return new Document(mProtoBuilder.build(), mPropertyBundle); - } - } - } - - /** - * Encapsulates a {@link Document} that represent an email. - * - * <p>This class is a higher level implement of {@link Document}. - * - * <p>This class will eventually migrate to Jetpack, where it will become public API. - * - * @hide - */ - public static class Email extends Document { - private static final String KEY_FROM = "from"; - private static final String KEY_TO = "to"; - private static final String KEY_CC = "cc"; - private static final String KEY_BCC = "bcc"; - private static final String KEY_SUBJECT = "subject"; - private static final String KEY_BODY = "body"; - - /** The name of the schema type for {@link Email} documents.*/ - public static final String SCHEMA_TYPE = "builtin:Email"; - - public static final AppSearchSchema SCHEMA = AppSearchSchema.newBuilder(SCHEMA_TYPE) - .addProperty(AppSearchSchema.newPropertyBuilder(KEY_FROM) - .setDataType(PropertyConfig.DATA_TYPE_STRING) - .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) - .build() - - ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_TO) - .setDataType(PropertyConfig.DATA_TYPE_STRING) - .setCardinality(PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) - .build() - - ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_CC) - .setDataType(PropertyConfig.DATA_TYPE_STRING) - .setCardinality(PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) - .build() - - ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_BCC) - .setDataType(PropertyConfig.DATA_TYPE_STRING) - .setCardinality(PropertyConfig.CARDINALITY_REPEATED) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) - .build() - - ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_SUBJECT) - .setDataType(PropertyConfig.DATA_TYPE_STRING) - .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) - .build() - - ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_BODY) - .setDataType(PropertyConfig.DATA_TYPE_STRING) - .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) - .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) - .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) - .build() - - ).build(); - - /** - * Creates a new {@link Email.Builder}. - * - * @param uri The uri of {@link Email}. - */ - public static Builder newBuilder(@NonNull String uri) { - return new Builder(uri); - } - - /** - * Creates a new {@link Email} from the contents of an existing {@link Document}. - * - * @param document The {@link Document} containing the email content. - */ - public Email(@NonNull Document document) { - super(document); - } - - /** - * Get the from address of {@link Email}. - * - * @return Returns the subject of {@link Email} or {@code null} if it's not been set yet. - * @hide - */ - @Nullable - public String getFrom() { - return getPropertyString(KEY_FROM); - } - - /** - * Get the destination addresses of {@link Email}. - * - * @return Returns the destination addresses of {@link Email} or {@code null} if it's not - * been set yet. - * @hide - */ - @Nullable - public String[] getTo() { - return getPropertyStringArray(KEY_TO); - } - - /** - * Get the CC list of {@link Email}. - * - * @return Returns the CC list of {@link Email} or {@code null} if it's not been set yet. - * @hide - */ - @Nullable - public String[] getCc() { - return getPropertyStringArray(KEY_CC); - } - - /** - * Get the BCC list of {@link Email}. - * - * @return Returns the BCC list of {@link Email} or {@code null} if it's not been set yet. - * @hide - */ - @Nullable - public String[] getBcc() { - return getPropertyStringArray(KEY_BCC); - } - - /** - * Get the subject of {@link Email}. - * - * @return Returns the value subject of {@link Email} or {@code null} if it's not been set - * yet. - * @hide - */ - @Nullable - public String getSubject() { - return getPropertyString(KEY_SUBJECT); - } - - /** - * Get the body of {@link Email}. - * - * @return Returns the body of {@link Email} or {@code null} if it's not been set yet. - * @hide - */ - @Nullable - public String getBody() { - return getPropertyString(KEY_BODY); - } - - /** - * The builder class for {@link Email}. - * @hide - */ - public static class Builder extends Document.Builder<Email.Builder> { - - /** - * Create a new {@link Email.Builder} - * @param uri The Uri of the Email. - * @hide - */ - private Builder(@NonNull String uri) { - super(uri, SCHEMA_TYPE); - } - - /** - * Set the from address of {@link Email} - * @hide - */ - @NonNull - public Email.Builder setFrom(@NonNull String from) { - setProperty(KEY_FROM, from); - return this; - } - - /** - * Set the destination address of {@link Email} - * @hide - */ - @NonNull - public Email.Builder setTo(@NonNull String... to) { - setProperty(KEY_TO, to); - return this; - } - - /** - * Set the CC list of {@link Email} - * @hide - */ - @NonNull - public Email.Builder setCc(@NonNull String... cc) { - setProperty(KEY_CC, cc); - return this; - } - - /** - * Set the BCC list of {@link Email} - * @hide - */ - @NonNull - public Email.Builder setBcc(@NonNull String... bcc) { - setProperty(KEY_BCC, bcc); - return this; - } - - /** - * Set the subject of {@link Email} - * @hide - */ - @NonNull - public Email.Builder setSubject(@NonNull String subject) { - setProperty(KEY_SUBJECT, subject); - return this; - } - - /** - * Set the body of {@link Email} - * @hide - */ - @NonNull - public Email.Builder setBody(@NonNull String body) { - setProperty(KEY_BODY, body); - return this; - } - - /** - * Builds the {@link Email} object. - * - * @hide - */ - @NonNull - @Override - public Email build() { - return new Email(super.build()); - } - } - } -} diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java new file mode 100644 index 000000000000..ff0f0dda55b9 --- /dev/null +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java @@ -0,0 +1,724 @@ +/* + * Copyright (C) 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.CurrentTimeMillisLong; +import android.annotation.DurationMillisLong; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; + +import com.google.android.icing.proto.DocumentProto; +import com.google.android.icing.proto.PropertyProto; +import com.google.android.icing.protobuf.ByteString; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Represents a document unit. + * + * <p>Documents are constructed via {@link AppSearchDocument.Builder}. + * @hide + */ +public class AppSearchDocument { + private static final String TAG = "AppSearchDocument"; + + /** + * The maximum number of elements in a repeatable field. Will reject the request if exceed + * this limit. + */ + private static final int MAX_REPEATED_PROPERTY_LENGTH = 100; + + /** + * The maximum {@link String#length} of a {@link String} field. Will reject the request if + * {@link String}s longer than this. + */ + private static final int MAX_STRING_LENGTH = 20_000; + + /** + * Contains {@link AppSearchDocument} basic information (uri, schemaType etc) and properties + * ordered by keys. + */ + @NonNull + private final DocumentProto mProto; + + /** Contains all properties in {@link #mProto} to support getting properties via keys. */ + @NonNull + private final Map<String, Object> mProperties; + + /** + * Create a new {@link AppSearchDocument}. + * @param proto Contains {@link AppSearchDocument} basic information (uri, schemaType etc) and + * properties ordered by keys. + * @param propertiesMap Contains all properties in {@link #mProto} to support get properties + * via keys. + */ + private AppSearchDocument(@NonNull DocumentProto proto, + @NonNull Map<String, Object> propertiesMap) { + mProto = proto; + mProperties = propertiesMap; + } + + /** + * Create a new {@link AppSearchDocument} from an existing instance. + * + * <p>This method should be only used by constructor of a subclass. + */ + protected AppSearchDocument(@NonNull AppSearchDocument document) { + this(document.mProto, document.mProperties); + } + + /** @hide */ + AppSearchDocument(@NonNull DocumentProto documentProto) { + this(documentProto, new ArrayMap<>()); + for (int i = 0; i < documentProto.getPropertiesCount(); i++) { + PropertyProto property = documentProto.getProperties(i); + String name = property.getName(); + if (property.getStringValuesCount() > 0) { + String[] values = new String[property.getStringValuesCount()]; + for (int j = 0; j < values.length; j++) { + values[j] = property.getStringValues(j); + } + mProperties.put(name, values); + } else if (property.getInt64ValuesCount() > 0) { + long[] values = new long[property.getInt64ValuesCount()]; + for (int j = 0; j < values.length; j++) { + values[j] = property.getInt64Values(j); + } + mProperties.put(property.getName(), values); + } else if (property.getDoubleValuesCount() > 0) { + double[] values = new double[property.getDoubleValuesCount()]; + for (int j = 0; j < values.length; j++) { + values[j] = property.getDoubleValues(j); + } + mProperties.put(property.getName(), values); + } else if (property.getBooleanValuesCount() > 0) { + boolean[] values = new boolean[property.getBooleanValuesCount()]; + for (int j = 0; j < values.length; j++) { + values[j] = property.getBooleanValues(j); + } + mProperties.put(property.getName(), values); + } else if (property.getBytesValuesCount() > 0) { + byte[][] values = new byte[property.getBytesValuesCount()][]; + for (int j = 0; j < values.length; j++) { + values[j] = property.getBytesValues(j).toByteArray(); + } + mProperties.put(name, values); + } else if (property.getDocumentValuesCount() > 0) { + AppSearchDocument[] values = + new AppSearchDocument[property.getDocumentValuesCount()]; + for (int j = 0; j < values.length; j++) { + values[j] = new AppSearchDocument(property.getDocumentValues(j)); + } + mProperties.put(name, values); + } else { + throw new IllegalStateException("Unknown type of value: " + name); + } + } + } + + /** + * Get the {@link DocumentProto} of the {@link AppSearchDocument}. + * + * <p>The {@link DocumentProto} contains {@link AppSearchDocument}'s basic information and all + * properties ordered by keys. + * @hide + */ + @NonNull + @VisibleForTesting + public DocumentProto getProto() { + return mProto; + } + + /** + * Get the uri of the {@link AppSearchDocument}. + * + * @hide + */ + @NonNull + public String getUri() { + return mProto.getUri(); + } + + /** + * Get the schema type of the {@link AppSearchDocument}. + * @hide + */ + @NonNull + public String getSchemaType() { + return mProto.getSchema(); + } + + /** + * Get the creation timestamp in milliseconds of the {@link AppSearchDocument}. Value will be in + * the {@link System#currentTimeMillis()} time base. + * + * @hide + */ + @CurrentTimeMillisLong + public long getCreationTimestampMillis() { + return mProto.getCreationTimestampMs(); + } + + /** + * Returns the TTL (Time To Live) of the {@link AppSearchDocument}, in milliseconds. + * + * <p>The default value is 0, which means the document is permanent and won't be auto-deleted + * until the app is uninstalled. + * + * @hide + */ + @DurationMillisLong + public long getTtlMillis() { + return mProto.getTtlMs(); + } + + /** + * Returns the score of the {@link AppSearchDocument}. + * + * <p>The score is a query-independent measure of the document's quality, relative to other + * {@link AppSearchDocument}s of the same type. + * + * <p>The default value is 0. + * + * @hide + */ + public int getScore() { + return mProto.getScore(); + } + + /** + * Retrieve a {@link String} value by key. + * + * @param key The key to look for. + * @return The first {@link String} associated with the given key or {@code null} if there + * is no such key or the value is of a different type. + * @hide + */ + @Nullable + public String getPropertyString(@NonNull String key) { + String[] propertyArray = getPropertyStringArray(key); + if (ArrayUtils.isEmpty(propertyArray)) { + return null; + } + warnIfSinglePropertyTooLong("String", key, propertyArray.length); + return propertyArray[0]; + } + + /** + * Retrieve a {@link Long} value by key. + * + * @param key The key to look for. + * @return The first {@link Long} associated with the given key or {@code null} if there + * is no such key or the value is of a different type. + * @hide + */ + @Nullable + public Long getPropertyLong(@NonNull String key) { + long[] propertyArray = getPropertyLongArray(key); + if (ArrayUtils.isEmpty(propertyArray)) { + return null; + } + warnIfSinglePropertyTooLong("Long", key, propertyArray.length); + return propertyArray[0]; + } + + /** + * Retrieve a {@link Double} value by key. + * + * @param key The key to look for. + * @return The first {@link Double} associated with the given key or {@code null} if there + * is no such key or the value is of a different type. + * @hide + */ + @Nullable + public Double getPropertyDouble(@NonNull String key) { + double[] propertyArray = getPropertyDoubleArray(key); + // TODO(tytytyww): Add support double array to ArraysUtils.isEmpty(). + if (propertyArray == null || propertyArray.length == 0) { + return null; + } + warnIfSinglePropertyTooLong("Double", key, propertyArray.length); + return propertyArray[0]; + } + + /** + * Retrieve a {@link Boolean} value by key. + * + * @param key The key to look for. + * @return The first {@link Boolean} associated with the given key or {@code null} if there + * is no such key or the value is of a different type. + * @hide + */ + @Nullable + public Boolean getPropertyBoolean(@NonNull String key) { + boolean[] propertyArray = getPropertyBooleanArray(key); + if (ArrayUtils.isEmpty(propertyArray)) { + return null; + } + warnIfSinglePropertyTooLong("Boolean", key, propertyArray.length); + return propertyArray[0]; + } + + /** + * Retrieve a {@code byte[]} value by key. + * + * @param key The key to look for. + * @return The first {@code byte[]} associated with the given key or {@code null} if there + * is no such key or the value is of a different type. + */ + @Nullable + public byte[] getPropertyBytes(@NonNull String key) { + byte[][] propertyArray = getPropertyBytesArray(key); + if (ArrayUtils.isEmpty(propertyArray)) { + return null; + } + warnIfSinglePropertyTooLong("ByteArray", key, propertyArray.length); + return propertyArray[0]; + } + + /** + * Retrieve a {@link AppSearchDocument} value by key. + * + * @param key The key to look for. + * @return The first {@link AppSearchDocument} associated with the given key or {@code null} if + * there is no such key or the value is of a different type. + */ + @Nullable + public AppSearchDocument getPropertyDocument(@NonNull String key) { + AppSearchDocument[] propertyArray = getPropertyDocumentArray(key); + if (ArrayUtils.isEmpty(propertyArray)) { + return null; + } + warnIfSinglePropertyTooLong("Document", key, propertyArray.length); + return propertyArray[0]; + } + + /** Prints a warning to logcat if the given propertyLength is greater than 1. */ + private static void warnIfSinglePropertyTooLong( + @NonNull String propertyType, @NonNull String key, int propertyLength) { + if (propertyLength > 1) { + Log.w(TAG, "The value for \"" + key + "\" contains " + propertyLength + + " elements. Only the first one will be returned from " + + "getProperty" + propertyType + "(). Try getProperty" + propertyType + + "Array()."); + } + } + + /** + * Retrieve a repeated {@code String} property by key. + * + * @param key The key to look for. + * @return The {@code String[]} associated with the given key, or {@code null} if no value + * is set or the value is of a different type. + * @hide + */ + @Nullable + public String[] getPropertyStringArray(@NonNull String key) { + return getAndCastPropertyArray(key, String[].class); + } + + /** + * Retrieve a repeated {@code long} property by key. + * + * @param key The key to look for. + * @return The {@code long[]} associated with the given key, or {@code null} if no value is + * set or the value is of a different type. + * @hide + */ + @Nullable + public long[] getPropertyLongArray(@NonNull String key) { + return getAndCastPropertyArray(key, long[].class); + } + + /** + * Retrieve a repeated {@code double} property by key. + * + * @param key The key to look for. + * @return The {@code double[]} associated with the given key, or {@code null} if no value + * is set or the value is of a different type. + * @hide + */ + @Nullable + public double[] getPropertyDoubleArray(@NonNull String key) { + return getAndCastPropertyArray(key, double[].class); + } + + /** + * Retrieve a repeated {@code boolean} property by key. + * + * @param key The key to look for. + * @return The {@code boolean[]} associated with the given key, or {@code null} if no value + * is set or the value is of a different type. + * @hide + */ + @Nullable + public boolean[] getPropertyBooleanArray(@NonNull String key) { + return getAndCastPropertyArray(key, boolean[].class); + } + + /** + * Retrieve a {@code byte[][]} property by key. + * + * @param key The key to look for. + * @return The {@code byte[][]} associated with the given key, or {@code null} if no value + * is set or the value is of a different type. + */ + @Nullable + public byte[][] getPropertyBytesArray(@NonNull String key) { + return getAndCastPropertyArray(key, byte[][].class); + } + + /** + * Retrieve a repeated {@link AppSearchDocument} property by key. + * + * @param key The key to look for. + * @return The {@link AppSearchDocument[]} associated with the given key, or {@code null} if no + * value is set or the value is of a different type. + */ + @Nullable + public AppSearchDocument[] getPropertyDocumentArray(@NonNull String key) { + return getAndCastPropertyArray(key, AppSearchDocument[].class); + } + + /** + * Gets a repeated property of the given key, and casts it to the given class type, which + * must be an array class type. + */ + @Nullable + private <T> T getAndCastPropertyArray(@NonNull String key, @NonNull Class<T> tClass) { + Object value = mProperties.get(key); + if (value == null) { + return null; + } + try { + return tClass.cast(value); + } catch (ClassCastException e) { + Log.w(TAG, "Error casting to requested type for key \"" + key + "\"", e); + return null; + } + } + + @Override + public boolean equals(@Nullable Object other) { + // Check only proto's equality is sufficient here since all properties in + // mProperties are ordered by keys and stored in proto. + if (this == other) { + return true; + } + if (!(other instanceof AppSearchDocument)) { + return false; + } + AppSearchDocument otherDocument = (AppSearchDocument) other; + return this.mProto.equals(otherDocument.mProto); + } + + @Override + public int hashCode() { + // Hash only proto is sufficient here since all properties in mProperties are ordered by + // keys and stored in proto. + return mProto.hashCode(); + } + + @Override + public String toString() { + return mProto.toString(); + } + + /** + * The builder class for {@link AppSearchDocument}. + * + * @param <BuilderType> Type of subclass who extend this. + * @hide + */ + public static class Builder<BuilderType extends Builder> { + + private final Map<String, Object> mProperties = new ArrayMap<>(); + private final DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder(); + private final BuilderType mBuilderTypeInstance; + + /** + * Create a new {@link AppSearchDocument.Builder}. + * + * @param uri The uri of {@link AppSearchDocument}. + * @param schemaType The schema type of the {@link AppSearchDocument}. The passed-in + * {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior + * to inserting a document of this {@code schemaType} into the AppSearch index using + * {@link AppSearchManager#putDocuments(List)}. Otherwise, the document will be + * rejected by {@link AppSearchManager#putDocuments(List)}. + * @hide + */ + public Builder(@NonNull String uri, @NonNull String schemaType) { + mBuilderTypeInstance = (BuilderType) this; + mProtoBuilder.setUri(uri).setSchema(schemaType); + // Set current timestamp for creation timestamp by default. + setCreationTimestampMillis(System.currentTimeMillis()); + } + + /** + * Sets the score of the {@link AppSearchDocument}. + * + * <p>The score is a query-independent measure of the document's quality, relative to + * other {@link AppSearchDocument}s of the same type. + * + * @throws IllegalArgumentException If the provided value is negative. + * @hide + */ + @NonNull + public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) { + if (score < 0) { + throw new IllegalArgumentException("Document score cannot be negative."); + } + mProtoBuilder.setScore(score); + return mBuilderTypeInstance; + } + + /** + * Set the creation timestamp in milliseconds of the {@link AppSearchDocument}. Should be + * set using a value obtained from the {@link System#currentTimeMillis()} time base. + * + * @hide + */ + @NonNull + public BuilderType setCreationTimestampMillis( + @CurrentTimeMillisLong long creationTimestampMillis) { + mProtoBuilder.setCreationTimestampMs(creationTimestampMillis); + return mBuilderTypeInstance; + } + + /** + * Set the TTL (Time To Live) of the {@link AppSearchDocument}, in milliseconds. + * + * <p>After this many milliseconds since the {@link #setCreationTimestampMillis(long)} + * creation timestamp}, the document is deleted. + * + * @param ttlMillis A non-negative duration in milliseconds. + * @throws IllegalArgumentException If the provided value is negative. + */ + @NonNull + public BuilderType setTtlMillis(@DurationMillisLong long ttlMillis) { + Preconditions.checkArgumentNonNegative( + ttlMillis, "Document ttlMillis cannot be negative."); + mProtoBuilder.setTtlMs(ttlMillis); + return mBuilderTypeInstance; + } + + /** + * Sets one or multiple {@code String} values for a property, replacing its previous + * values. + * + * @param key The key associated with the {@code values}. + * @param values The {@code String} values of the property. + * @hide + */ + @NonNull + public BuilderType setProperty(@NonNull String key, @NonNull String... values) { + putInPropertyMap(key, values); + return mBuilderTypeInstance; + } + + /** + * Sets one or multiple {@code boolean} values for a property, replacing its previous + * values. + * + * @param key The key associated with the {@code values}. + * @param values The {@code boolean} values of the property. + */ + @NonNull + public BuilderType setProperty(@NonNull String key, @NonNull boolean... values) { + putInPropertyMap(key, values); + return mBuilderTypeInstance; + } + + /** + * Sets one or multiple {@code long} values for a property, replacing its previous + * values. + * + * @param key The key associated with the {@code values}. + * @param values The {@code long} values of the property. + */ + @NonNull + public BuilderType setProperty(@NonNull String key, @NonNull long... values) { + putInPropertyMap(key, values); + return mBuilderTypeInstance; + } + + /** + * Sets one or multiple {@code double} values for a property, replacing its previous + * values. + * + * @param key The key associated with the {@code values}. + * @param values The {@code double} values of the property. + */ + @NonNull + public BuilderType setProperty(@NonNull String key, @NonNull double... values) { + putInPropertyMap(key, values); + return mBuilderTypeInstance; + } + + /** + * Sets one or multiple {@code byte[]} for a property, replacing its previous values. + * + * @param key The key associated with the {@code values}. + * @param values The {@code byte[]} of the property. + */ + @NonNull + public BuilderType setProperty(@NonNull String key, @NonNull byte[]... values) { + putInPropertyMap(key, values); + return mBuilderTypeInstance; + } + + /** + * Sets one or multiple {@link AppSearchDocument} values for a property, replacing its + * previous values. + * + * @param key The key associated with the {@code values}. + * @param values The {@link AppSearchDocument} values of the property. + */ + @NonNull + public BuilderType setProperty(@NonNull String key, @NonNull AppSearchDocument... values) { + putInPropertyMap(key, values); + return mBuilderTypeInstance; + } + + private void putInPropertyMap(@NonNull String key, @NonNull String[] values) + throws IllegalArgumentException { + Objects.requireNonNull(key); + Objects.requireNonNull(values); + validateRepeatedPropertyLength(key, values.length); + for (int i = 0; i < values.length; i++) { + if (values[i] == null) { + throw new IllegalArgumentException("The String at " + i + " is null."); + } else if (values[i].length() > MAX_STRING_LENGTH) { + throw new IllegalArgumentException("The String at " + i + " length is: " + + values[i].length() + ", which exceeds length limit: " + + MAX_STRING_LENGTH + "."); + } + } + mProperties.put(key, values); + } + + private void putInPropertyMap(@NonNull String key, @NonNull boolean[] values) { + Objects.requireNonNull(key); + Objects.requireNonNull(values); + validateRepeatedPropertyLength(key, values.length); + mProperties.put(key, values); + } + + private void putInPropertyMap(@NonNull String key, @NonNull double[] values) { + Objects.requireNonNull(key); + Objects.requireNonNull(values); + validateRepeatedPropertyLength(key, values.length); + mProperties.put(key, values); + } + + private void putInPropertyMap(@NonNull String key, @NonNull long[] values) { + Objects.requireNonNull(key); + Objects.requireNonNull(values); + validateRepeatedPropertyLength(key, values.length); + mProperties.put(key, values); + } + + private void putInPropertyMap(@NonNull String key, @NonNull byte[][] values) { + Objects.requireNonNull(key); + Objects.requireNonNull(values); + validateRepeatedPropertyLength(key, values.length); + mProperties.put(key, values); + } + + private void putInPropertyMap(@NonNull String key, @NonNull AppSearchDocument[] values) { + Objects.requireNonNull(key); + Objects.requireNonNull(values); + for (int i = 0; i < values.length; i++) { + if (values[i] == null) { + throw new IllegalArgumentException("The document at " + i + " is null."); + } + } + validateRepeatedPropertyLength(key, values.length); + mProperties.put(key, values); + } + + private static void validateRepeatedPropertyLength(@NonNull String key, int length) { + if (length == 0) { + throw new IllegalArgumentException("The input array is empty."); + } else if (length > MAX_REPEATED_PROPERTY_LENGTH) { + throw new IllegalArgumentException( + "Repeated property \"" + key + "\" has length " + length + + ", which exceeds the limit of " + + MAX_REPEATED_PROPERTY_LENGTH); + } + } + + /** + * Builds the {@link AppSearchDocument} object. + * @hide + */ + public AppSearchDocument build() { + // Build proto by sorting the keys in mProperties to exclude the influence of + // order. Therefore documents will generate same proto as long as the contents are + // same. Note that the order of repeated fields is still preserved. + ArrayList<String> keys = new ArrayList<>(mProperties.keySet()); + Collections.sort(keys); + for (int i = 0; i < keys.size(); i++) { + String name = keys.get(i); + Object values = mProperties.get(name); + PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(name); + if (values instanceof boolean[]) { + for (boolean value : (boolean[]) values) { + propertyProto.addBooleanValues(value); + } + } else if (values instanceof long[]) { + for (long value : (long[]) values) { + propertyProto.addInt64Values(value); + } + } else if (values instanceof double[]) { + for (double value : (double[]) values) { + propertyProto.addDoubleValues(value); + } + } else if (values instanceof String[]) { + for (String value : (String[]) values) { + propertyProto.addStringValues(value); + } + } else if (values instanceof AppSearchDocument[]) { + for (AppSearchDocument value : (AppSearchDocument[]) values) { + propertyProto.addDocumentValues(value.getProto()); + } + } else if (values instanceof byte[][]) { + for (byte[] value : (byte[][]) values) { + propertyProto.addBytesValues(ByteString.copyFrom(value)); + } + } else { + throw new IllegalStateException( + "Property \"" + name + "\" has unsupported value type \"" + + values.getClass().getSimpleName() + "\""); + } + mProtoBuilder.addProperties(propertyProto); + } + return new AppSearchDocument(mProtoBuilder.build(), mProperties); + } + } +} diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java new file mode 100644 index 000000000000..5b9457b77ea0 --- /dev/null +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 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.NonNull; +import android.annotation.Nullable; +import android.app.appsearch.AppSearchSchema.PropertyConfig; + +/** + * Encapsulates a {@link AppSearchDocument} that represent an email. + * + * <p>This class is a higher level implement of {@link AppSearchDocument}. + * + * <p>This class will eventually migrate to Jetpack, where it will become public API. + * + * @hide + */ +public class AppSearchEmail extends AppSearchDocument { + private static final String KEY_FROM = "from"; + private static final String KEY_TO = "to"; + private static final String KEY_CC = "cc"; + private static final String KEY_BCC = "bcc"; + private static final String KEY_SUBJECT = "subject"; + private static final String KEY_BODY = "body"; + + /** The name of the schema type for {@link AppSearchEmail} documents.*/ + public static final String SCHEMA_TYPE = "builtin:Email"; + + public static final AppSearchSchema SCHEMA = AppSearchSchema.newBuilder(SCHEMA_TYPE) + .addProperty(AppSearchSchema.newPropertyBuilder(KEY_FROM) + .setDataType(PropertyConfig.DATA_TYPE_STRING) + .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .build() + + ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_TO) + .setDataType(PropertyConfig.DATA_TYPE_STRING) + .setCardinality(PropertyConfig.CARDINALITY_REPEATED) + .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .build() + + ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_CC) + .setDataType(PropertyConfig.DATA_TYPE_STRING) + .setCardinality(PropertyConfig.CARDINALITY_REPEATED) + .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .build() + + ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_BCC) + .setDataType(PropertyConfig.DATA_TYPE_STRING) + .setCardinality(PropertyConfig.CARDINALITY_REPEATED) + .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .build() + + ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_SUBJECT) + .setDataType(PropertyConfig.DATA_TYPE_STRING) + .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .build() + + ).addProperty(AppSearchSchema.newPropertyBuilder(KEY_BODY) + .setDataType(PropertyConfig.DATA_TYPE_STRING) + .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) + .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) + .build() + + ).build(); + + /** + * Creates a new {@link AppSearchEmail} from the contents of an existing + * {@link AppSearchDocument}. + * + * @param document The {@link AppSearchDocument} containing the email content. + */ + public AppSearchEmail(@NonNull AppSearchDocument document) { + super(document); + } + + /** + * Get the from address of {@link AppSearchEmail}. + * + * @return Returns the subject of {@link AppSearchEmail} or {@code null} if it's not been set + * yet. + * @hide + */ + @Nullable + public String getFrom() { + return getPropertyString(KEY_FROM); + } + + /** + * Get the destination addresses of {@link AppSearchEmail}. + * + * @return Returns the destination addresses of {@link AppSearchEmail} or {@code null} if it's + * not been set yet. + * @hide + */ + @Nullable + public String[] getTo() { + return getPropertyStringArray(KEY_TO); + } + + /** + * Get the CC list of {@link AppSearchEmail}. + * + * @return Returns the CC list of {@link AppSearchEmail} or {@code null} if it's not been set + * yet. + * @hide + */ + @Nullable + public String[] getCc() { + return getPropertyStringArray(KEY_CC); + } + + /** + * Get the BCC list of {@link AppSearchEmail}. + * + * @return Returns the BCC list of {@link AppSearchEmail} or {@code null} if it's not been set + * yet. + * @hide + */ + @Nullable + public String[] getBcc() { + return getPropertyStringArray(KEY_BCC); + } + + /** + * Get the subject of {@link AppSearchEmail}. + * + * @return Returns the value subject of {@link AppSearchEmail} or {@code null} if it's not been + * set yet. + * @hide + */ + @Nullable + public String getSubject() { + return getPropertyString(KEY_SUBJECT); + } + + /** + * Get the body of {@link AppSearchEmail}. + * + * @return Returns the body of {@link AppSearchEmail} or {@code null} if it's not been set yet. + * @hide + */ + @Nullable + public String getBody() { + return getPropertyString(KEY_BODY); + } + + /** + * The builder class for {@link AppSearchEmail}. + * @hide + */ + public static class Builder extends AppSearchDocument.Builder<AppSearchEmail.Builder> { + + /** + * Create a new {@link AppSearchEmail.Builder} + * @param uri The Uri of the Email. + * @hide + */ + public Builder(@NonNull String uri) { + super(uri, SCHEMA_TYPE); + } + + /** + * Set the from address of {@link AppSearchEmail} + * @hide + */ + @NonNull + public AppSearchEmail.Builder setFrom(@NonNull String from) { + setProperty(KEY_FROM, from); + return this; + } + + /** + * Set the destination address of {@link AppSearchEmail} + * @hide + */ + @NonNull + public AppSearchEmail.Builder setTo(@NonNull String... to) { + setProperty(KEY_TO, to); + return this; + } + + /** + * Set the CC list of {@link AppSearchEmail} + * @hide + */ + @NonNull + public AppSearchEmail.Builder setCc(@NonNull String... cc) { + setProperty(KEY_CC, cc); + return this; + } + + /** + * Set the BCC list of {@link AppSearchEmail} + * @hide + */ + @NonNull + public AppSearchEmail.Builder setBcc(@NonNull String... bcc) { + setProperty(KEY_BCC, bcc); + return this; + } + + /** + * Set the subject of {@link AppSearchEmail} + * @hide + */ + @NonNull + public AppSearchEmail.Builder setSubject(@NonNull String subject) { + setProperty(KEY_SUBJECT, subject); + return this; + } + + /** + * Set the body of {@link AppSearchEmail} + * @hide + */ + @NonNull + public AppSearchEmail.Builder setBody(@NonNull String body) { + setProperty(KEY_BODY, body); + return this; + } + + /** + * Builds the {@link AppSearchEmail} object. + * + * @hide + */ + @NonNull + @Override + public AppSearchEmail build() { + return new AppSearchEmail(super.build()); + } + } +} diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index 15c336820df7..e2c9b0f74870 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -18,7 +18,6 @@ package android.app.appsearch; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.SystemService; -import android.app.appsearch.AppSearch.Document; import android.content.Context; import android.os.RemoteException; @@ -150,25 +149,26 @@ public class AppSearchManager { } /** - * Index {@link android.app.appsearch.AppSearch.Document Documents} into AppSearch. + * Index {@link AppSearchDocument Documents} into AppSearch. * * <p>You should not call this method directly; instead, use the * {@code AppSearch#putDocuments()} API provided by JetPack. * - * <p>Each {@link AppSearch.Document Document's} {@code schemaType} field must be set to the + * <p>Each {@link AppSearchDocument Document's} {@code schemaType} field must be set to the * name of a schema type previously registered via the {@link #setSchema} method. * - * @param documents {@link Document Documents} that need to be indexed. + * @param documents {@link AppSearchDocument Documents} that need to be indexed. * @return An {@link AppSearchBatchResult} mapping the document URIs to {@link Void} if they * were successfully indexed, or a {@link Throwable} describing the failure if they could * not be indexed. * @hide */ - public AppSearchBatchResult<String, Void> putDocuments(@NonNull List<Document> documents) { + public AppSearchBatchResult<String, Void> putDocuments( + @NonNull List<AppSearchDocument> documents) { // TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in // one big list. List<byte[]> documentsBytes = new ArrayList<>(documents.size()); - for (Document document : documents) { + for (AppSearchDocument document : documents) { documentsBytes.add(document.getProto().toByteArray()); } AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>(); diff --git a/apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java b/apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java index 6aa91a3fe9e4..5ce296082d70 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java +++ b/apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java @@ -70,7 +70,7 @@ public final class MatchInfo { private final String mPropertyPath; private final SnippetMatchProto mSnippetMatch; - private final AppSearch.Document mDocument; + private final AppSearchDocument mDocument; /** * List of content with same property path in a document when there are multiple matches in * repeated sections. @@ -79,7 +79,7 @@ public final class MatchInfo { /** @hide */ public MatchInfo(@NonNull String propertyPath, @NonNull SnippetMatchProto snippetMatch, - @NonNull AppSearch.Document document) { + @NonNull AppSearchDocument document) { mPropertyPath = propertyPath; mSnippetMatch = snippetMatch; mDocument = document; diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java index f48ebde288f3..7287fe68f519 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java +++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java @@ -71,21 +71,21 @@ public final class SearchResults implements Iterator<SearchResults.Result> { private final SearchResultProto.ResultProto mResultProto; @Nullable - private AppSearch.Document mDocument; + private AppSearchDocument mDocument; private Result(SearchResultProto.ResultProto resultProto) { mResultProto = resultProto; } /** - * Contains the matching {@link AppSearch.Document}. + * Contains the matching {@link AppSearchDocument}. * @return Document object which matched the query. * @hide */ @NonNull - public AppSearch.Document getDocument() { + public AppSearchDocument getDocument() { if (mDocument == null) { - mDocument = new AppSearch.Document(mResultProto.getDocument()); + mDocument = new AppSearchDocument(mResultProto.getDocument()); } return mDocument; } @@ -106,7 +106,7 @@ public final class SearchResults implements Iterator<SearchResults.Result> { if (!mResultProto.hasSnippet()) { return null; } - AppSearch.Document document = getDocument(); + AppSearchDocument document = getDocument(); List<MatchInfo> matchList = new ArrayList<>(); for (Iterator entryProtoIterator = mResultProto.getSnippet() .getEntriesList().iterator(); entryProtoIterator.hasNext(); ) { diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java index abba7fcce6c7..4a4f13676cb3 100644 --- a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java @@ -20,12 +20,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.assertThrows; -import android.app.appsearch.AppSearch.Document; - import androidx.test.filters.SmallTest; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.PropertyProto; +import com.google.android.icing.protobuf.ByteString; import org.junit.Test; @@ -36,22 +35,36 @@ import java.util.List; @SmallTest public class AppSearchDocumentTest { + private static final byte[] sByteArray1 = new byte[]{(byte) 1, (byte) 2, (byte) 3}; + private static final byte[] sByteArray2 = new byte[]{(byte) 4, (byte) 5, (byte) 6}; + private static final AppSearchDocument sDocumentProperties1 = new AppSearchDocument + .Builder("sDocumentProperties1", "sDocumentPropertiesSchemaType1") + .build(); + private static final AppSearchDocument sDocumentProperties2 = new AppSearchDocument + .Builder("sDocumentProperties2", "sDocumentPropertiesSchemaType2") + .build(); @Test public void testDocumentEquals_Identical() { - Document document1 = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document1 = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) + .setTtlMillis(1L) .setProperty("longKey1", 1L, 2L, 3L) .setProperty("doubleKey1", 1.0, 2.0, 3.0) .setProperty("booleanKey1", true, false, true) .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .setProperty("byteKey1", sByteArray1, sByteArray2) + .setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2) .build(); - Document document2 = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document2 = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) + .setTtlMillis(1L) .setProperty("longKey1", 1L, 2L, 3L) .setProperty("doubleKey1", 1.0, 2.0, 3.0) .setProperty("booleanKey1", true, false, true) .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .setProperty("byteKey1", sByteArray1, sByteArray2) + .setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2) .build(); assertThat(document1).isEqualTo(document2); assertThat(document1.hashCode()).isEqualTo(document2.hashCode()); @@ -59,20 +72,24 @@ public class AppSearchDocumentTest { @Test public void testDocumentEquals_DifferentOrder() { - Document document1 = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document1 = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) .setProperty("longKey1", 1L, 2L, 3L) + .setProperty("byteKey1", sByteArray1, sByteArray2) .setProperty("doubleKey1", 1.0, 2.0, 3.0) .setProperty("booleanKey1", true, false, true) + .setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2) .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") .build(); // Create second document with same parameter but different order. - Document document2 = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document2 = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) .setProperty("booleanKey1", true, false, true) + .setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2) .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") .setProperty("doubleKey1", 1.0, 2.0, 3.0) + .setProperty("byteKey1", sByteArray1, sByteArray2) .setProperty("longKey1", 1L, 2L, 3L) .build(); assertThat(document1).isEqualTo(document2); @@ -81,13 +98,13 @@ public class AppSearchDocumentTest { @Test public void testDocumentEquals_Failure() { - Document document1 = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document1 = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) .setProperty("longKey1", 1L, 2L, 3L) .build(); // Create second document with same order but different value. - Document document2 = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document2 = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) .setProperty("longKey1", 1L, 2L, 4L) // Different .build(); @@ -97,13 +114,13 @@ public class AppSearchDocumentTest { @Test public void testDocumentEquals_Failure_RepeatedFieldOrder() { - Document document1 = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document1 = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) .setProperty("booleanKey1", true, false, true) .build(); // Create second document with same order but different value. - Document document2 = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document2 = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) .setProperty("booleanKey1", true, true, false) // Different .build(); @@ -113,14 +130,19 @@ public class AppSearchDocumentTest { @Test public void testDocumentGetSingleValue() { - Document document = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) .setScore(1) + .setTtlMillis(1L) .setProperty("longKey1", 1L) .setProperty("doubleKey1", 1.0) .setProperty("booleanKey1", true) - .setProperty("stringKey1", "test-value1").build(); + .setProperty("stringKey1", "test-value1") + .setProperty("byteKey1", sByteArray1) + .setProperty("documentKey1", sDocumentProperties1) + .build(); assertThat(document.getUri()).isEqualTo("uri1"); + assertThat(document.getTtlMillis()).isEqualTo(1L); assertThat(document.getSchemaType()).isEqualTo("schemaType1"); assertThat(document.getCreationTimestampMillis()).isEqualTo(5); assertThat(document.getScore()).isEqualTo(1); @@ -128,16 +150,21 @@ public class AppSearchDocumentTest { assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0); assertThat(document.getPropertyBoolean("booleanKey1")).isTrue(); assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1"); + assertThat(document.getPropertyBytes("byteKey1")) + .asList().containsExactly((byte) 1, (byte) 2, (byte) 3); + assertThat(document.getPropertyDocument("documentKey1")).isEqualTo(sDocumentProperties1); } @Test public void testDocumentGetArrayValues() { - Document document = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) .setProperty("longKey1", 1L, 2L, 3L) .setProperty("doubleKey1", 1.0, 2.0, 3.0) .setProperty("booleanKey1", true, false, true) .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .setProperty("byteKey1", sByteArray1, sByteArray2) + .setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2) .build(); assertThat(document.getUri()).isEqualTo("uri1"); @@ -149,11 +176,15 @@ public class AppSearchDocumentTest { .containsExactly(true, false, true); assertThat(document.getPropertyStringArray("stringKey1")).asList() .containsExactly("test-value1", "test-value2", "test-value3"); + assertThat(document.getPropertyBytesArray("byteKey1")).asList() + .containsExactly(sByteArray1, sByteArray2); + assertThat(document.getPropertyDocumentArray("documentKey1")).asList() + .containsExactly(sDocumentProperties1, sDocumentProperties2); } @Test public void testDocumentGetValues_DifferentTypes() { - Document document = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document = new AppSearchDocument.Builder("uri1", "schemaType1") .setScore(1) .setProperty("longKey1", 1L) .setProperty("booleanKey1", true, false, true) @@ -180,25 +211,32 @@ public class AppSearchDocumentTest { @Test public void testDocumentInvalid() { - Document.Builder builder = Document.newBuilder("uri1", "schemaType1"); + AppSearchDocument.Builder builder = new AppSearchDocument.Builder("uri1", "schemaType1"); assertThrows( IllegalArgumentException.class, () -> builder.setProperty("test", new boolean[]{})); } @Test public void testDocumentProtoPopulation() { - Document document = Document.newBuilder("uri1", "schemaType1") + AppSearchDocument document = new AppSearchDocument.Builder("uri1", "schemaType1") .setCreationTimestampMillis(5L) .setScore(1) + .setTtlMillis(1L) .setProperty("longKey1", 1L) .setProperty("doubleKey1", 1.0) .setProperty("booleanKey1", true) .setProperty("stringKey1", "test-value1") + .setProperty("byteKey1", sByteArray1) + .setProperty("documentKey1", sDocumentProperties1) .build(); // Create the Document proto. Need to sort the property order by key. DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder() - .setUri("uri1").setSchema("schemaType1").setScore(1).setCreationTimestampMs(5L); + .setUri("uri1") + .setSchema("schemaType1") + .setCreationTimestampMs(5L) + .setScore(1) + .setTtlMs(1L); HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>(); propertyProtoMap.put("longKey1", PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L)); @@ -208,6 +246,12 @@ public class AppSearchDocumentTest { PropertyProto.newBuilder().setName("booleanKey1").addBooleanValues(true)); propertyProtoMap.put("stringKey1", PropertyProto.newBuilder().setName("stringKey1").addStringValues("test-value1")); + propertyProtoMap.put("byteKey1", + PropertyProto.newBuilder().setName("byteKey1").addBytesValues( + ByteString.copyFrom(sByteArray1))); + propertyProtoMap.put("documentKey1", + PropertyProto.newBuilder().setName("documentKey1") + .addDocumentValues(sDocumentProperties1.getProto())); List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet()); Collections.sort(sortedKey); for (String key : sortedKey) { diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java index c50b1da71d02..6aa16cc1e323 100644 --- a/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java @@ -18,8 +18,6 @@ package android.app.appsearch; import static com.google.common.truth.Truth.assertThat; -import android.app.appsearch.AppSearch.Email; - import androidx.test.filters.SmallTest; import org.junit.Test; @@ -29,7 +27,7 @@ public class AppSearchEmailTest { @Test public void testBuildEmailAndGetValue() { - Email email = Email.newBuilder("uri") + AppSearchEmail email = new AppSearchEmail.Builder("uri") .setFrom("FakeFromAddress") .setCc("CC1", "CC2") // Score and Property are mixed into the middle to make sure DocumentBuilder's diff --git a/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java index c5986bb2f07b..b29483c2e3b3 100644 --- a/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java +++ b/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java @@ -19,28 +19,42 @@ package android.app.appsearch.impl; import static com.google.common.truth.Truth.assertThat; import android.annotation.NonNull; -import android.app.appsearch.AppSearch.Document; +import android.app.appsearch.AppSearchDocument; import androidx.test.filters.SmallTest; import org.junit.Test; -/** Tests that {@link Document} and {@link Document.Builder} are extendable by developers. +/** + * Tests that {@link AppSearchDocument} and {@link AppSearchDocument.Builder} are extendable by + * developers. * - * <p>This class is intentionally in a different package than {@link Document} to make sure there - * are no package-private methods required for external developers to add custom types. + * <p>This class is intentionally in a different package than {@link AppSearchDocument} to make sure + * there are no package-private methods required for external developers to add custom types. */ @SmallTest public class CustomerDocumentTest { + + private static byte[] sByteArray1 = new byte[]{(byte) 1, (byte) 2, (byte) 3}; + private static byte[] sByteArray2 = new byte[]{(byte) 4, (byte) 5, (byte) 6}; + private static AppSearchDocument sDocumentProperties1 = new AppSearchDocument + .Builder("sDocumentProperties1", "sDocumentPropertiesSchemaType1") + .build(); + private static AppSearchDocument sDocumentProperties2 = new AppSearchDocument + .Builder("sDocumentProperties2", "sDocumentPropertiesSchemaType2") + .build(); + @Test public void testBuildCustomerDocument() { - CustomerDocument customerDocument = CustomerDocument.newBuilder("uri1") + CustomerDocument customerDocument = new CustomerDocument.Builder("uri1") .setScore(1) .setCreationTimestampMillis(0) .setProperty("longKey1", 1L, 2L, 3L) .setProperty("doubleKey1", 1.0, 2.0, 3.0) .setProperty("booleanKey1", true, false, true) .setProperty("stringKey1", "test-value1", "test-value2", "test-value3") + .setProperty("byteKey1", sByteArray1, sByteArray2) + .setProperty("documentKey1", sDocumentProperties1, sDocumentProperties2) .build(); assertThat(customerDocument.getUri()).isEqualTo("uri1"); @@ -55,22 +69,22 @@ public class CustomerDocumentTest { .containsExactly(true, false, true); assertThat(customerDocument.getPropertyStringArray("stringKey1")).asList() .containsExactly("test-value1", "test-value2", "test-value3"); + assertThat(customerDocument.getPropertyBytesArray("byteKey1")).asList() + .containsExactly(sByteArray1, sByteArray2); + assertThat(customerDocument.getPropertyDocumentArray("documentKey1")).asList() + .containsExactly(sDocumentProperties1, sDocumentProperties2); } /** * An example document type for test purposes, defined outside of * {@link android.app.appsearch.AppSearch} (the way an external developer would define it). */ - private static class CustomerDocument extends Document { - private CustomerDocument(Document document) { + private static class CustomerDocument extends AppSearchDocument { + private CustomerDocument(AppSearchDocument document) { super(document); } - public static CustomerDocument.Builder newBuilder(String uri) { - return new CustomerDocument.Builder(uri); - } - - public static class Builder extends Document.Builder<CustomerDocument.Builder> { + public static class Builder extends AppSearchDocument.Builder<CustomerDocument.Builder> { private Builder(@NonNull String uri) { super(uri, "customerDocument"); } |