diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java deleted file mode 100644 index 7d2b64e5d882b..0000000000000 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java +++ /dev/null @@ -1,698 +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.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.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. - * - *
Documents are constructed via {@link AppSearchDocument.Builder}.
- * @hide
- */
-public class AppSearchDocument {
- private static final String TAG = "AppSearchDocument";
-
- /** The default empty namespace.*/
- // TODO(adorokhine): Allow namespace to be specified in the document.
- public static final String DEFAULT_NAMESPACE = "";
-
- /**
- * 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 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);
- }
- }
- }
-
- /**
- * Returns the {@link DocumentProto} of the {@link AppSearchDocument}.
- *
- * The {@link DocumentProto} contains {@link AppSearchDocument}'s basic information and all
- * properties ordered by keys.
- * @hide
- */
- @NonNull
- @VisibleForTesting
- public DocumentProto getProto() {
- return mProto;
- }
-
- /** Returns the URI of the {@link AppSearchDocument}. */
- @NonNull
- public String getUri() {
- return mProto.getUri();
- }
-
- /** Returns the schema type of the {@link AppSearchDocument}. */
- @NonNull
- public String getSchemaType() {
- return mProto.getSchema();
- }
-
- /**
- * Returns the creation timestamp in milliseconds of the {@link AppSearchDocument}. Value will
- * be in the {@link System#currentTimeMillis()} time base.
- */
- @CurrentTimeMillisLong
- public long getCreationTimestampMillis() {
- return mProto.getCreationTimestampMs();
- }
-
- /**
- * Returns the TTL (Time To Live) of the {@link AppSearchDocument}, in milliseconds.
- *
- * The default value is 0, which means the document is permanent and won't be auto-deleted
- * until the app is uninstalled.
- */
- @DurationMillisLong
- public long getTtlMillis() {
- return mProto.getTtlMs();
- }
-
- /**
- * Returns the score of the {@link AppSearchDocument}.
- *
- * The score is a query-independent measure of the document's quality, relative to other
- * {@link AppSearchDocument}s of the same type.
- *
- * The default value is 0.
- */
- 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.
- */
- @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 {@code long} value by key.
- *
- * @param key The key to look for.
- * @return The first {@code long} associated with the given key or default value {@code 0} if
- * there is no such key or the value is of a different type.
- */
- public long getPropertyLong(@NonNull String key) {
- long[] propertyArray = getPropertyLongArray(key);
- if (ArrayUtils.isEmpty(propertyArray)) {
- return 0;
- }
- warnIfSinglePropertyTooLong("Long", key, propertyArray.length);
- return propertyArray[0];
- }
-
- /**
- * Retrieve a {@code double} value by key.
- *
- * @param key The key to look for.
- * @return The first {@code double} associated with the given key or default value {@code 0.0}
- * if there is no such key or the value is of a different type.
- */
- 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 0.0;
- }
- warnIfSinglePropertyTooLong("Double", key, propertyArray.length);
- return propertyArray[0];
- }
-
- /**
- * Retrieve a {@code boolean} value by key.
- *
- * @param key The key to look for.
- * @return The first {@code boolean} associated with the given key or default value
- * {@code false} if there is no such key or the value is of a different type.
- */
- public boolean getPropertyBoolean(@NonNull String key) {
- boolean[] propertyArray = getPropertyBooleanArray(key);
- if (ArrayUtils.isEmpty(propertyArray)) {
- return false;
- }
- 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 {@link 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.
- */
- @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.
- */
- @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.
- */
- @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.
- */
- @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 The URI is a unique string opaque to AppSearch.
- *
- * @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)}.
- */
- public Builder(@NonNull String uri, @NonNull String schemaType) {
- mBuilderTypeInstance = (BuilderType) this;
- mProtoBuilder.setUri(uri).setSchema(schemaType).setNamespace(DEFAULT_NAMESPACE);
- // Set current timestamp for creation timestamp by default.
- setCreationTimestampMillis(System.currentTimeMillis());
- }
-
- /**
- * Sets the score of the {@link AppSearchDocument}.
- *
- * 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.
- */
- @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.
- */
- @NonNull
- public BuilderType setCreationTimestampMillis(
- @CurrentTimeMillisLong long creationTimestampMillis) {
- mProtoBuilder.setCreationTimestampMs(creationTimestampMillis);
- return mBuilderTypeInstance;
- }
-
- /**
- * Set the TTL (Time To Live) of the {@link AppSearchDocument}, in milliseconds.
- *
- * 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.
- */
- @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. */
- @NonNull
- 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 This class is a higher level implement of {@link AppSearchDocument}.
+ * This class is a higher level implement of {@link GenericDocument}.
*
* This class will eventually migrate to Jetpack, where it will become public API.
*
* @hide
*/
-public class AppSearchEmail extends AppSearchDocument {
+
+public class AppSearchEmail extends GenericDocument {
+ /** The name of the schema type for {@link AppSearchEmail} documents.*/
+ public static final String SCHEMA_TYPE = "builtin:Email";
+
private static final String KEY_FROM = "from";
private static final String KEY_TO = "to";
private static final String KEY_CC = "cc";
@@ -37,46 +43,43 @@ public class AppSearchEmail extends AppSearchDocument {
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 = new AppSearchSchema.Builder(SCHEMA_TYPE)
- .addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_FROM)
+ .addProperty(new PropertyConfig.Builder(KEY_FROM)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_TO)
+ ).addProperty(new PropertyConfig.Builder(KEY_TO)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_CC)
+ ).addProperty(new PropertyConfig.Builder(KEY_CC)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_BCC)
+ ).addProperty(new PropertyConfig.Builder(KEY_BCC)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_SUBJECT)
+ ).addProperty(new PropertyConfig.Builder(KEY_SUBJECT)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder(KEY_BODY)
+ ).addProperty(new PropertyConfig.Builder(KEY_BODY)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
@@ -87,12 +90,11 @@ public class AppSearchEmail extends AppSearchDocument {
/**
* Creates a new {@link AppSearchEmail} from the contents of an existing
- * {@link AppSearchDocument}.
+ * {@link GenericDocument}.
*
- * @param document The {@link AppSearchDocument} containing the email content.
- * @hide
+ * @param document The {@link GenericDocument} containing the email content.
*/
- public AppSearchEmail(@NonNull AppSearchDocument document) {
+ public AppSearchEmail(@NonNull GenericDocument document) {
super(document);
}
@@ -101,7 +103,6 @@ public class AppSearchEmail extends AppSearchDocument {
*
* @return Returns the subject of {@link AppSearchEmail} or {@code null} if it's not been set
* yet.
- * @hide
*/
@Nullable
public String getFrom() {
@@ -113,7 +114,6 @@ public class AppSearchEmail extends AppSearchDocument {
*
* @return Returns the destination addresses of {@link AppSearchEmail} or {@code null} if it's
* not been set yet.
- * @hide
*/
@Nullable
public String[] getTo() {
@@ -125,7 +125,6 @@ public class AppSearchEmail extends AppSearchDocument {
*
* @return Returns the CC list of {@link AppSearchEmail} or {@code null} if it's not been set
* yet.
- * @hide
*/
@Nullable
public String[] getCc() {
@@ -137,7 +136,6 @@ public class AppSearchEmail extends AppSearchDocument {
*
* @return Returns the BCC list of {@link AppSearchEmail} or {@code null} if it's not been set
* yet.
- * @hide
*/
@Nullable
public String[] getBcc() {
@@ -149,7 +147,6 @@ public class AppSearchEmail extends AppSearchDocument {
*
* @return Returns the value subject of {@link AppSearchEmail} or {@code null} if it's not been
* set yet.
- * @hide
*/
@Nullable
public String getSubject() {
@@ -160,7 +157,6 @@ public class AppSearchEmail extends AppSearchDocument {
* 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() {
@@ -169,14 +165,12 @@ public class AppSearchEmail extends AppSearchDocument {
/**
* The builder class for {@link AppSearchEmail}.
- * @hide
*/
- public static class Builder extends AppSearchDocument.Builder You should not call this method directly; instead, use the
* {@code AppSearch#putDocuments()} API provided by JetPack.
*
- * Each {@link AppSearchDocument}'s {@code schemaType} field must be set to the name of a
+ * Each {@link GenericDocument}'s {@code schemaType} field must be set to the name of a
* schema type previously registered via the {@link #setSchema} method.
*
- * @param documents {@link AppSearchDocument}s that need to be indexed.
+ * @param documents {@link GenericDocument}s 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 You should not call this method directly; instead, use the
* {@code AppSearch#getDocuments()} API provided by JetPack.
*
* @param uris URIs of the documents to look up.
* @return An {@link AppSearchBatchResult} mapping the document URIs to
- * {@link AppSearchDocument} values if they were successfully retrieved, a {@code null}
+ * {@link GenericDocument} values if they were successfully retrieved, a {@code null}
* failure if they were not found, or a {@link Throwable} failure describing the problem if
* an error occurred.
*/
- public AppSearchBatchResult You should not call this method directly; instead, use the {@code AppSearch#delete()} API
* provided by JetPack.
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java
new file mode 100644
index 0000000000000..9fe2c67d00f27
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/GenericDocument.java
@@ -0,0 +1,923 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.util.Log;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import android.app.appsearch.exceptions.AppSearchException;
+import com.android.internal.util.Preconditions;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Represents a document unit.
+ *
+ * Documents are constructed via {@link GenericDocument.Builder}.
+ * @hide
+ */
+public class GenericDocument {
+ private static final String TAG = "GenericDocument";
+
+ /** The default empty namespace.*/
+ public static final String DEFAULT_NAMESPACE = "";
+
+ /**
+ * 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;
+
+ /** The maximum number of indexed properties a document can have. */
+ private static final int MAX_INDEXED_PROPERTIES = 16;
+
+ /** The default score of document. */
+ private static final int DEFAULT_SCORE = 0;
+
+ /** The default time-to-live in millisecond of a document, which is infinity. */
+ private static final long DEFAULT_TTL_MILLIS = 0L;
+
+ /** @hide */
+
+ public static final String PROPERTIES_FIELD = "properties";
+
+ /** @hide */
+
+ public static final String BYTE_ARRAY_FIELD = "byteArray";
+
+ static final String SCHEMA_TYPE_FIELD = "schemaType";
+ static final String URI_FIELD = "uri";
+ static final String SCORE_FIELD = "score";
+ static final String TTL_MILLIS_FIELD = "ttlMillis";
+ static final String CREATION_TIMESTAMP_MILLIS_FIELD = "creationTimestampMillis";
+ static final String NAMESPACE_FIELD = "namespace";
+
+ /**
+ * The maximum number of indexed properties a document can have.
+ *
+ * Indexed properties are properties where the
+ * {@link android.app.appsearch.annotation.AppSearchDocument.Property#indexingType} constant is
+ * anything other than {@link
+ * android.app.appsearch.AppSearchSchema.PropertyConfig.IndexingType#INDEXING_TYPE_NONE}.
+ */
+ public static int getMaxIndexedProperties() {
+ return MAX_INDEXED_PROPERTIES;
+ }
+
+ /** Contains {@link GenericDocument} basic information (uri, schemaType etc).*/
+ @NonNull
+ final Bundle mBundle;
+
+ /** Contains all properties in {@link GenericDocument} to support getting properties via keys.*/
+ @NonNull
+ private final Bundle mProperties;
+
+ @NonNull
+ private final String mUri;
+ @NonNull
+ private final String mSchemaType;
+ private final long mCreationTimestampMillis;
+ @Nullable
+ private Integer mHashCode;
+
+ /**
+ * Rebuilds a {@link GenericDocument} by the a bundle.
+ * @param bundle Contains {@link GenericDocument} basic information (uri, schemaType etc) and
+ * a properties bundle contains all properties in {@link GenericDocument} to
+ * support getting properties via keys.
+ * @hide
+ */
+
+ public GenericDocument(@NonNull Bundle bundle) {
+ Preconditions.checkNotNull(bundle);
+ mBundle = bundle;
+ mProperties = Preconditions.checkNotNull(bundle.getParcelable(PROPERTIES_FIELD));
+ mUri = Preconditions.checkNotNull(mBundle.getString(URI_FIELD));
+ mSchemaType = Preconditions.checkNotNull(mBundle.getString(SCHEMA_TYPE_FIELD));
+ mCreationTimestampMillis = mBundle.getLong(CREATION_TIMESTAMP_MILLIS_FIELD,
+ System.currentTimeMillis());
+ }
+
+ /**
+ * Creates a new {@link GenericDocument} from an existing instance.
+ *
+ * This method should be only used by constructor of a subclass.
+ */
+ protected GenericDocument(@NonNull GenericDocument document) {
+ this(document.mBundle);
+ }
+
+ /**
+ * Returns the {@link Bundle} populated by this builder.
+ * @hide
+ */
+
+ @NonNull
+ public Bundle getBundle() {
+ return mBundle;
+ }
+
+ /** Returns the URI of the {@link GenericDocument}. */
+ @NonNull
+ public String getUri() {
+ return mUri;
+ }
+
+ /** Returns the namespace of the {@link GenericDocument}. */
+ @NonNull
+ public String getNamespace() {
+ return mBundle.getString(NAMESPACE_FIELD, DEFAULT_NAMESPACE);
+ }
+
+ /** Returns the schema type of the {@link GenericDocument}. */
+ @NonNull
+ public String getSchemaType() {
+ return mSchemaType;
+ }
+
+ /** Returns the creation timestamp of the {@link GenericDocument}, in milliseconds. */
+ public long getCreationTimestampMillis() {
+ return mCreationTimestampMillis;
+ }
+
+ /**
+ * Returns the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
+ *
+ * The default value is 0, which means the document is permanent and won't be auto-deleted
+ * until the app is uninstalled.
+ */
+ public long getTtlMillis() {
+ return mBundle.getLong(TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS);
+ }
+
+ /**
+ * Returns the score of the {@link GenericDocument}.
+ *
+ * The score is a query-independent measure of the document's quality, relative to other
+ * {@link GenericDocument}s of the same type.
+ *
+ * The default value is 0.
+ */
+ public int getScore() {
+ return mBundle.getInt(SCORE_FIELD, DEFAULT_SCORE);
+ }
+
+ /**
+ * Retrieves 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.
+ */
+ @Nullable
+ public String getPropertyString(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ String[] propertyArray = getPropertyStringArray(key);
+ if (propertyArray == null || propertyArray.length == 0) {
+ return null;
+ }
+ warnIfSinglePropertyTooLong("String", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieves a {@code long} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@code long} associated with the given key or default value {@code 0} if
+ * there is no such key or the value is of a different type.
+ */
+ public long getPropertyLong(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ long[] propertyArray = getPropertyLongArray(key);
+ if (propertyArray == null || propertyArray.length == 0) {
+ return 0;
+ }
+ warnIfSinglePropertyTooLong("Long", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieves a {@code double} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@code double} associated with the given key or default value {@code 0.0}
+ * if there is no such key or the value is of a different type.
+ */
+ public double getPropertyDouble(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ double[] propertyArray = getPropertyDoubleArray(key);
+ if (propertyArray == null || propertyArray.length == 0) {
+ return 0.0;
+ }
+ warnIfSinglePropertyTooLong("Double", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieves a {@code boolean} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@code boolean} associated with the given key or default value
+ * {@code false} if there is no such key or the value is of a different type.
+ */
+ public boolean getPropertyBoolean(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ boolean[] propertyArray = getPropertyBooleanArray(key);
+ if (propertyArray == null || propertyArray.length == 0) {
+ return false;
+ }
+ warnIfSinglePropertyTooLong("Boolean", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieves 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) {
+ Preconditions.checkNotNull(key);
+ byte[][] propertyArray = getPropertyBytesArray(key);
+ if (propertyArray == null || propertyArray.length == 0) {
+ return null;
+ }
+ warnIfSinglePropertyTooLong("ByteArray", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieves a {@link GenericDocument} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@link GenericDocument} associated with the given key or {@code null} if
+ * there is no such key or the value is of a different type.
+ */
+ @Nullable
+ public GenericDocument getPropertyDocument(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ GenericDocument[] propertyArray = getPropertyDocumentArray(key);
+ if (propertyArray == null || propertyArray.length == 0) {
+ 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().");
+ }
+ }
+
+ /**
+ * Retrieves 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.
+ */
+ @Nullable
+ public String[] getPropertyStringArray(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ return getAndCastPropertyArray(key, String[].class);
+ }
+
+ /**
+ * Retrieves a repeated {@link String} 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.
+ */
+ @Nullable
+ public long[] getPropertyLongArray(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ return getAndCastPropertyArray(key, long[].class);
+ }
+
+ /**
+ * Retrieves 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.
+ */
+ @Nullable
+ public double[] getPropertyDoubleArray(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ return getAndCastPropertyArray(key, double[].class);
+ }
+
+ /**
+ * Retrieves 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.
+ */
+ @Nullable
+ public boolean[] getPropertyBooleanArray(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ return getAndCastPropertyArray(key, boolean[].class);
+ }
+
+ /**
+ * Retrieves 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.
+ */
+ @SuppressLint("ArrayReturn")
+ @Nullable
+ @SuppressWarnings("unchecked")
+ public byte[][] getPropertyBytesArray(@NonNull String key) {
+ Preconditions.checkNotNull(key);
+ ArrayList Two bundle will be considered equally if they contains same content.
+ */
+ @SuppressWarnings("unchecked")
+ private static boolean bundleEquals(Bundle one, Bundle two) {
+ if (one.size() != two.size()) {
+ return false;
+ }
+ Set The hash code is only effected by the content in the bundle. Bundles will get
+ * consistent hash code if they have same content.
+ */
+ @SuppressWarnings("unchecked")
+ private static int bundleHashCode(Bundle bundle) {
+ int[] hashCodes = new int[bundle.size()];
+ int i = 0;
+ // Bundle inherit its hashCode() from Object.java, which only relative to their memory
+ // address. Bundle doesn't have an order, so we should iterate all keys and combine
+ // their value's hashcode into an array. And use the hashcode of the array to be
+ // the hashcode of the bundle.
+ for (String key : bundle.keySet()) {
+ Object value = bundle.get(key);
+ if (value instanceof boolean[]) {
+ hashCodes[i++] = Arrays.hashCode((boolean[]) value);
+ } else if (value instanceof long[]) {
+ hashCodes[i++] = Arrays.hashCode((long[]) value);
+ } else if (value instanceof double[]) {
+ hashCodes[i++] = Arrays.hashCode((double[]) value);
+ } else if (value instanceof String[]) {
+ hashCodes[i++] = Arrays.hashCode((Object[]) value);
+ } else if (value instanceof Bundle) {
+ hashCodes[i++] = bundleHashCode((Bundle) value);
+ } else if (value instanceof Bundle[]) {
+ Bundle[] bundles = (Bundle[]) value;
+ int[] innerHashCodes = new int[bundles.length];
+ for (int j = 0; j < innerHashCodes.length; j++) {
+ innerHashCodes[j] = bundleHashCode(bundles[j]);
+ }
+ hashCodes[i++] = Arrays.hashCode(innerHashCodes);
+ } else if (value instanceof ArrayList) {
+ ArrayList The score is a query-independent measure of the document's quality, relative to
+ * other {@link GenericDocument}s of the same type.
+ *
+ * @throws IllegalArgumentException If the provided value is negative.
+ */
+ @NonNull
+ public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ if (score < 0) {
+ throw new IllegalArgumentException("Document score cannot be negative.");
+ }
+ mBundle.putInt(GenericDocument.SCORE_FIELD, score);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Set the creation timestamp in milliseconds of the {@link GenericDocument}. Should be
+ * set using a value obtained from the {@link System#currentTimeMillis()} time base.
+ */
+ @NonNull
+ public BuilderType setCreationTimestampMillis(long creationTimestampMillis) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBundle.putLong(GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD,
+ creationTimestampMillis);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Set the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
+ *
+ * After this many milliseconds since the {@link #setCreationTimestampMillis 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(long ttlMillis) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ if (ttlMillis < 0) {
+ throw new IllegalArgumentException("Document ttlMillis cannot be negative.");
+ }
+ mBundle.putLong(GenericDocument.TTL_MILLIS_FIELD, 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.
+ */
+ @NonNull
+ public BuilderType setProperty(@NonNull String key, @NonNull String... values) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(values);
+ putInPropertyBundle(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) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(values);
+ putInPropertyBundle(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) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(values);
+ putInPropertyBundle(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) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(values);
+ putInPropertyBundle(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) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(values);
+ putInPropertyBundle(key, values);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Sets one or multiple {@link GenericDocument} values for a property, replacing its
+ * previous values.
+ *
+ * @param key The key associated with the {@code values}.
+ * @param values The {@link GenericDocument} values of the property.
+ */
+ @NonNull
+ public BuilderType setProperty(@NonNull String key, @NonNull GenericDocument... values) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(values);
+ putInPropertyBundle(key, values);
+ return mBuilderTypeInstance;
+ }
+
+ private void putInPropertyBundle(@NonNull String key, @NonNull String[] values)
+ throws IllegalArgumentException {
+ 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.putStringArray(key, values);
+ }
+
+ private void putInPropertyBundle(@NonNull String key, @NonNull boolean[] values) {
+ validateRepeatedPropertyLength(key, values.length);
+ mProperties.putBooleanArray(key, values);
+ }
+
+ private void putInPropertyBundle(@NonNull String key, @NonNull double[] values) {
+ validateRepeatedPropertyLength(key, values.length);
+ mProperties.putDoubleArray(key, values);
+ }
+
+ private void putInPropertyBundle(@NonNull String key, @NonNull long[] values) {
+ validateRepeatedPropertyLength(key, values.length);
+ mProperties.putLongArray(key, values);
+ }
+
+ /**
+ * Converts and saves a byte[][] into {@link #mProperties}.
+ *
+ * Bundle doesn't support for two dimension array byte[][], we are converting byte[][]
+ * into ArrayList Class Example 1:
- * A document contains following text in property subject:
- * A commonly used fake word is foo. Another nonsense word that’s used a lot is bar.
- *
- * If the queryExpression is "foo".
- *
- * {@link MatchInfo#getPropertyPath()} returns "subject"
- * {@link MatchInfo#getFullText()} returns "A commonly used fake word is foo. Another nonsense
- * word that’s used a lot is bar."
- * {@link MatchInfo#getExactMatchPosition()} returns [29, 32]
- * {@link MatchInfo#getExactMatch()} returns "foo"
- * {@link MatchInfo#getSnippetPosition()} returns [29, 41]
- * {@link MatchInfo#getSnippet()} returns "is foo. Another"
- *
- * Class Example 2:
- * A document contains a property name sender which contains 2 property names name and email, so
- * we will have 2 property paths: {@code sender.name} and {@code sender.email}.
- * Let {@code sender.name = "Test Name Jr."} and {@code sender.email = "TestNameJr@gmail.com"}
- *
- * If the queryExpression is "Test". We will have 2 matches.
- *
- * Match-1
- * {@link MatchInfo#getPropertyPath()} returns "sender.name"
- * {@link MatchInfo#getFullText()} returns "Test Name Jr."
- * {@link MatchInfo#getExactMatchPosition()} returns [0, 4]
- * {@link MatchInfo#getExactMatch()} returns "Test"
- * {@link MatchInfo#getSnippetPosition()} returns [0, 9]
- * {@link MatchInfo#getSnippet()} returns "Test Name Jr."
- * Match-2
- * {@link MatchInfo#getPropertyPath()} returns "sender.email"
- * {@link MatchInfo#getFullText()} returns "TestNameJr@gmail.com"
- * {@link MatchInfo#getExactMatchPosition()} returns [0, 20]
- * {@link MatchInfo#getExactMatch()} returns "TestNameJr@gmail.com"
- * {@link MatchInfo#getSnippetPosition()} returns [0, 20]
- * {@link MatchInfo#getSnippet()} returns "TestNameJr@gmail.com"
- * @hide
- */
-// TODO(sidchhabra): Capture real snippet after integration with icingLib.
-public final class MatchInfo {
-
- private final String mPropertyPath;
- private final SnippetMatchProto mSnippetMatch;
- private final AppSearchDocument mDocument;
- /**
- * List of content with same property path in a document when there are multiple matches in
- * repeated sections.
- */
- private final String[] mValues;
-
- /** @hide */
- public MatchInfo(@NonNull String propertyPath, @NonNull SnippetMatchProto snippetMatch,
- @NonNull AppSearchDocument document) {
- mPropertyPath = propertyPath;
- mSnippetMatch = snippetMatch;
- mDocument = document;
- // In IcingLib snippeting is available for only 3 data types i.e String, double and long,
- // so we need to check which of these three are requested.
- // TODO (sidchhabra): getPropertyStringArray takes property name, handle for property path.
- String[] values = mDocument.getPropertyStringArray(propertyPath);
- if (values == null) {
- values = doubleToString(mDocument.getPropertyDoubleArray(propertyPath));
- }
- if (values == null) {
- values = longToString(mDocument.getPropertyLongArray(propertyPath));
- }
- if (values == null) {
- throw new IllegalStateException("No content found for requested property path!");
- }
- mValues = values;
- }
-
- /**
- * Gets the property path corresponding to the given entry.
- * Property Path: '.' - delimited sequence of property names indicating which property in
- * the Document these snippets correspond to.
- * Example properties: 'body', 'sender.name', 'sender.emailaddress', etc.
- * For class example 1 this returns "subject"
- */
- @NonNull
- public String getPropertyPath() {
- return mPropertyPath;
- }
-
- /**
- * Gets the full text corresponding to the given entry.
- * For class example this returns "A commonly used fake word is foo. Another nonsense word
- * that’s used a lot is bar."
- */
- @NonNull
- public String getFullText() {
- return mValues[mSnippetMatch.getValuesIndex()];
- }
-
- /**
- * Gets the exact match range corresponding to the given entry.
- * For class example 1 this returns [29, 32]
- */
- @NonNull
- public Range getExactMatchPosition() {
- return new Range(mSnippetMatch.getExactMatchPosition(),
- mSnippetMatch.getExactMatchPosition() + mSnippetMatch.getExactMatchBytes());
- }
-
- /**
- * Gets the exact match corresponding to the given entry.
- * For class example 1 this returns "foo"
- */
- @NonNull
- public CharSequence getExactMatch() {
- return getSubstring(getExactMatchPosition());
- }
-
- /**
- * Gets the snippet range corresponding to the given entry.
- * For class example 1 this returns [29, 41]
- */
- @NonNull
- public Range getSnippetPosition() {
- return new Range(mSnippetMatch.getWindowPosition(),
- mSnippetMatch.getWindowPosition() + mSnippetMatch.getWindowBytes());
- }
-
- /**
- * Gets the snippet corresponding to the given entry.
- * Snippet - Provides a subset of the content to display. The
- * length of this content can be changed {@link SearchSpec.Builder#setMaxSnippetSize(int)}.
- * Windowing is centered around the middle of the matched token with content on either side
- * clipped to token boundaries.
- * For class example 1 this returns "foo. Another"
- */
- @NonNull
- public CharSequence getSnippet() {
- return getSubstring(getSnippetPosition());
- }
-
- private CharSequence getSubstring(Range range) {
- return getFullText()
- .substring((int) range.getLower(), (int) range.getUpper());
- }
-
- /** Utility method to convert double[] to String[] */
- private String[] doubleToString(double[] values) {
- //TODO(sidchhabra): Implement the method.
- return null;
- }
-
- /** Utility method to convert long[] to String[] */
- private String[] longToString(long[] values) {
- //TODO(sidchhabra): Implement the method.
- return null;
- }
-}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java
new file mode 100644
index 0000000000000..758280bbc3227
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResult.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.os.Bundle;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Objects;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents one of the results obtained from the query.
+ *
+ * It contains the document which matched, information about which section(s) in the document
+ * matched, and snippet information containing textual summaries of the document's match(es).
+ * @hide
+ */
+public final class SearchResult {
+ /** @hide */
+
+ public static final String DOCUMENT_FIELD = "document";
+
+ /** @hide */
+
+ public static final String MATCHES_FIELD = "matches";
+
+ @NonNull
+ private final Bundle mBundle;
+
+ @NonNull
+ private final Bundle mDocumentBundle;
+
+ @Nullable
+ private GenericDocument mDocument;
+
+ @Nullable
+ private final List Class Example 1:
+ * A document contains following text in property subject:
+ * A commonly used fake word is foo. Another nonsense word that’s used a lot is bar.
+ *
+ * If the queryExpression is "foo".
+ *
+ * {@link MatchInfo#getPropertyPath()} returns "subject"
+ * {@link MatchInfo#getFullText()} returns "A commonly used fake word is foo. Another
+ * nonsense word that’s used a lot is bar."
+ * {@link MatchInfo#getExactMatchPosition()} returns [29, 32]
+ * {@link MatchInfo#getExactMatch()} returns "foo"
+ * {@link MatchInfo#getSnippetPosition()} returns [26, 33]
+ * {@link MatchInfo#getSnippet()} returns "is foo."
+ *
+ * Class Example 2:
+ * A document contains a property name sender which contains 2 property names name and email, so
+ * we will have 2 property paths: {@code sender.name} and {@code sender.email}.
+ * Let {@code sender.name = "Test Name Jr."} and
+ * {@code sender.email = "TestNameJr@gmail.com"}
+ *
+ * If the queryExpression is "Test". We will have 2 matches.
+ *
+ * Match-1
+ * {@link MatchInfo#getPropertyPath()} returns "sender.name"
+ * {@link MatchInfo#getFullText()} returns "Test Name Jr."
+ * {@link MatchInfo#getExactMatchPosition()} returns [0, 4]
+ * {@link MatchInfo#getExactMatch()} returns "Test"
+ * {@link MatchInfo#getSnippetPosition()} returns [0, 9]
+ * {@link MatchInfo#getSnippet()} returns "Test Name"
+ * Match-2
+ * {@link MatchInfo#getPropertyPath()} returns "sender.email"
+ * {@link MatchInfo#getFullText()} returns "TestNameJr@gmail.com"
+ * {@link MatchInfo#getExactMatchPosition()} returns [0, 20]
+ * {@link MatchInfo#getExactMatch()} returns "TestNameJr@gmail.com"
+ * {@link MatchInfo#getSnippetPosition()} returns [0, 20]
+ * {@link MatchInfo#getSnippet()} returns "TestNameJr@gmail.com"
+ */
+ public static final class MatchInfo {
+ /**
+ * The path of the matching snippet property.
+ * @hide
+ */
+
+ public static final String PROPERTY_PATH_FIELD = "propertyPath";
+
+ /**
+ * The index of matching value in its property. A property may have multiple values. This
+ * index indicates which value is the match.
+ * @hide
+ */
+
+ public static final String VALUES_INDEX_FIELD = "valuesIndex";
+
+ /** @hide */
+
+ public static final String EXACT_MATCH_POSITION_LOWER_FIELD = "exactMatchPositionLower";
+
+ /** @hide */
+
+ public static final String EXACT_MATCH_POSITION_UPPER_FIELD = "exactMatchPositionUpper";
+
+ /** @hide */
+
+ public static final String WINDOW_POSITION_LOWER_FIELD = "windowPositionLower";
+
+ /** @hide */
+
+ public static final String WINDOW_POSITION_UPPER_FIELD = "windowPositionUpper";
+
+ private final String mFullText;
+ private final String mPropertyPath;
+ private final Bundle mBundle;
+ private MatchRange mExactMatchRange;
+ private MatchRange mWindowRange;
+
+ MatchInfo(@NonNull GenericDocument document, @NonNull Bundle bundle) {
+ mBundle = Preconditions.checkNotNull(bundle);
+ Preconditions.checkNotNull(document);
+ mPropertyPath = Preconditions.checkNotNull(bundle.getString(PROPERTY_PATH_FIELD));
+ mFullText = getPropertyValues(
+ document, mPropertyPath, mBundle.getInt(VALUES_INDEX_FIELD));
+ }
+
+ /**
+ * Gets the property path corresponding to the given entry.
+ * Property Path: '.' - delimited sequence of property names indicating which property in
+ * the Document these snippets correspond to.
+ * Example properties: 'body', 'sender.name', 'sender.emailaddress', etc.
+ * For class example 1 this returns "subject"
+ */
+ @NonNull
+ public String getPropertyPath() {
+ return mPropertyPath;
+ }
+
+ /**
+ * Gets the full text corresponding to the given entry.
+ * For class example this returns "A commonly used fake word is foo. Another nonsense
+ * word that's used a lot is bar."
+ */
+ @NonNull
+ public String getFullText() {
+ return mFullText;
+ }
+
+ /**
+ * Gets the exact {@link MatchRange} corresponding to the given entry.
+ * For class example 1 this returns [29, 32]
+ */
+ @NonNull
+ public MatchRange getExactMatchPosition() {
+ if (mExactMatchRange == null) {
+ mExactMatchRange = new MatchRange(
+ mBundle.getInt(EXACT_MATCH_POSITION_LOWER_FIELD),
+ mBundle.getInt(EXACT_MATCH_POSITION_UPPER_FIELD));
+ }
+ return mExactMatchRange;
+ }
+
+ /**
+ * Gets the {@link MatchRange} corresponding to the given entry.
+ * For class example 1 this returns "foo"
+ */
+ @NonNull
+ public CharSequence getExactMatch() {
+ return getSubstring(getExactMatchPosition());
+ }
+
+ /**
+ * Gets the snippet {@link MatchRange} corresponding to the given entry.
+ * Only populated when set maxSnippetSize > 0 in
+ * {@link SearchSpec.Builder#setMaxSnippetSize}.
+ * For class example 1 this returns [29, 41].
+ */
+ @NonNull
+ public MatchRange getSnippetPosition() {
+ if (mWindowRange == null) {
+ mWindowRange = new MatchRange(
+ mBundle.getInt(WINDOW_POSITION_LOWER_FIELD),
+ mBundle.getInt(WINDOW_POSITION_UPPER_FIELD));
+ }
+ return mWindowRange;
+ }
+
+ /**
+ * Gets the snippet corresponding to the given entry.
+ * Snippet - Provides a subset of the content to display. Only populated when requested
+ * maxSnippetSize > 0. The size of this content can be changed by
+ * {@link SearchSpec.Builder#setMaxSnippetSize}. Windowing is centered around the middle of
+ * the matched token with content on either side clipped to token boundaries.
+ * For class example 1 this returns "foo. Another"
+ */
+ @NonNull
+ public CharSequence getSnippet() {
+ return getSubstring(getSnippetPosition());
+ }
+
+ private CharSequence getSubstring(MatchRange range) {
+ return getFullText().substring(range.getStart(), range.getEnd());
+ }
+
+ /** Extracts the matching string from the document. */
+ private static String getPropertyValues(
+ GenericDocument document, String propertyName, int valueIndex) {
+ // In IcingLib snippeting is available for only 3 data types i.e String, double and
+ // long, so we need to check which of these three are requested.
+ // TODO (tytytyww): getPropertyStringArray takes property name, handle for property
+ // path.
+ // TODO (tytytyww): support double[] and long[].
+ String[] values = document.getPropertyStringArray(propertyName);
+ if (values == null) {
+ throw new IllegalStateException("No content found for requested property path!");
+ }
+ return values[valueIndex];
+ }
+ }
+
+ /**
+ * Class providing the position range of matching information.
+ *
+ * All ranges are finite, and the left side of the range is always {@code <=} the right
+ * side of the range.
+ *
+ * Example: MatchRange(0, 100) represent a hundred ints from 0 to 99."
+ *
+ */
+ public static final class MatchRange {
+ private final int mEnd;
+ private final int mStart;
+
+ /**
+ * Creates a new immutable range.
+ * The endpoints are {@code [start, end)}; that is the range is bounded. {@code start}
+ * must be lesser or equal to {@code end}.
+ *
+ * @param start The start point (inclusive)
+ * @param end The end point (exclusive)
+ * @hide
+ */
+
+ public MatchRange(int start, int end) {
+ if (start > end) {
+ throw new IllegalArgumentException("Start point must be less than or equal to "
+ + "end point");
+ }
+ mStart = start;
+ mEnd = end;
+ }
+
+ /** Gets the start point (inclusive). */
+ public int getStart() {
+ return mStart;
+ }
+
+ /** Gets the end point (exclusive). */
+ public int getEnd() {
+ return mEnd;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof MatchRange)) {
+ return false;
+ }
+ MatchRange otherMatchRange = (MatchRange) other;
+ return this.getStart() == otherMatchRange.getStart()
+ && this.getEnd() == otherMatchRange.getEnd();
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "MatchRange { start: " + mStart + " , end: " + mEnd + "}";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStart, mEnd);
+ }
+ }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
index 7287fe68f5190..9f376250f1a68 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
@@ -17,112 +17,62 @@
package android.app.appsearch;
import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.google.android.icing.proto.SearchResultProto;
-import com.google.android.icing.proto.SnippetMatchProto;
-import com.google.android.icing.proto.SnippetProto;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
-import java.util.NoSuchElementException;
/**
- * SearchResults are a list of results that are returned from a query. Each result from this
- * list contains a document and may contain other fields like snippets based on request.
- * This iterator class is not thread safe.
+ * Structure for transmitting a page of search results across binder.
* @hide
*/
-public final class SearchResults implements Iterator If set to 0 (default), snippeting is disabled and
- * {@link SearchResults.Result#getMatches} will return {@code null} for that result.
+ *
+ * If set to 0 (default), snippeting is disabled and {@link SearchResult#getMatches} will
+ * return {@code null} for that result.
+ *
* The value should be set in range[0, 10k].
*/
@NonNull
@@ -264,8 +266,10 @@ public final class SearchSpec {
/**
* Only the first {@code matchesCountPerProperty} matches for a every property of
* {@link GenericDocument} will contain snippet information.
- * If set to 0, snippeting is disabled and {@link SearchResults.Result#getMatches}
- * will return {@code null} for that result.
+ *
+ * If set to 0, snippeting is disabled and {@link SearchResult#getMatches} will return
+ * {@code null} for that result.
+ *
* The value should be set in range[0, 10k].
*/
@NonNull
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 4bc0c39bb6c91..06612ac31f636 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -17,10 +17,12 @@ package com.android.server.appsearch;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchDocument;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
import android.app.appsearch.IAppSearchManager;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
@@ -32,7 +34,9 @@ import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
import com.android.server.appsearch.external.localbackend.AppSearchImpl;
+import com.android.server.appsearch.external.localbackend.converter.GenericDocumentToProtoConverter;
import com.android.server.appsearch.external.localbackend.converter.SchemaToProtoConverter;
+import com.android.server.appsearch.external.localbackend.converter.SearchResultToProtoConverter;
import com.android.server.appsearch.external.localbackend.converter.SearchSpecToProtoConverter;
import com.google.android.icing.proto.DocumentProto;
@@ -80,7 +84,7 @@ public class AppSearchManagerService extends SystemService {
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
String databaseName = makeDatabaseName(callingUid);
impl.setSchema(databaseName, schemaProtoBuilder.build(), forceOverride);
- callback.complete(AppSearchResult.newSuccessfulResult(/*value=*/ null));
+ callback.complete(AppSearchResult.newSuccessfulResult(/*result=*/ null));
} catch (Throwable t) {
callback.complete(throwableToFailedResult(t));
} finally {
@@ -90,9 +94,9 @@ public class AppSearchManagerService extends SystemService {
@Override
public void putDocuments(
- @NonNull List documentsBytes,
+ @NonNull List This class is intentionally in a different package than {@link AppSearchDocument} to make sure
+ * This class is intentionally in a different package than {@link GenericDocument} 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
+ private static GenericDocument sDocumentProperties1 = new GenericDocument
.Builder("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
.build();
- private static AppSearchDocument sDocumentProperties2 = new AppSearchDocument
+ private static GenericDocument sDocumentProperties2 = new GenericDocument
.Builder("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
.build();
@@ -77,19 +74,21 @@ public class CustomerDocumentTest {
/**
* An example document type for test purposes, defined outside of
- * {@link android.app.appsearch.AppSearch} (the way an external developer would define it).
+ * {@link GenericDocument} (the way an external developer would define
+ * it).
*/
- private static class CustomerDocument extends AppSearchDocument {
- private CustomerDocument(AppSearchDocument document) {
+ private static class CustomerDocument extends GenericDocument {
+ private CustomerDocument(GenericDocument document) {
super(document);
}
- public static class Builder extends AppSearchDocument.Builder> query(
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
// TODO(b/146386470): Transmit the result documents as a RemoteStream instead of sending
// them in one big list.
- AndroidFuture