summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/AppSearch.java854
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java724
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/AppSearchEmail.java255
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java12
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/MatchInfo.java4
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/SearchResults.java10
-rw-r--r--core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java78
-rw-r--r--core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java4
-rw-r--r--core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java38
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");
}