Add AppSearch.java, containing the Document class, base Builder, and Email.
Document is the basic entity to be indexed and queried in AppSearch. AppSearch.java contains all AppSearch Document types and their Builders. They are grouped here to avoid potential collisions with other generically-named classes like Email and Contact). Bug: 143789408 Test: atest FrameworksCoreTests:android.app.appsearch Change-Id: I7bb607ad4114451a3db11b9ade9c197da1e6556a
This commit is contained in:
committed by
Terry Wang
parent
9ebb72c7cc
commit
0731f57193
@@ -0,0 +1,762 @@
|
||||
/*
|
||||
* 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.CurrentTimeSecondsLong;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
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;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 seconds of the {@link Document}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
// TODO(b/143789408) Change seconds to millis with Icing library.
|
||||
@CurrentTimeSecondsLong
|
||||
public long getCreationTimestampSecs() {
|
||||
return mProto.getCreationTimestampSecs();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
setCreationTimestampSecs(
|
||||
TimeUnit.MILLISECONDS.toSeconds(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 seconds of the {@link Document}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public BuilderType setCreationTimestampSecs(
|
||||
@CurrentTimeSecondsLong long creationTimestampSecs) {
|
||||
mProtoBuilder.setCreationTimestampSecs(creationTimestampSecs);
|
||||
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 {
|
||||
|
||||
/** The name of the schema type for {@link Email} 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";
|
||||
private static final String KEY_BCC = "bcc";
|
||||
private static final String KEY_SUBJECT = "subject";
|
||||
private static final String KEY_BODY = "body";
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Email.Builder}.
|
||||
*
|
||||
* @param uri The uri of {@link Email}.
|
||||
*/
|
||||
public static Builder newBuilder(@NonNull String uri) {
|
||||
return new Builder(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 address of {@link Email}.
|
||||
*
|
||||
* @return Returns the destination address 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ 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;
|
||||
|
||||
@@ -25,6 +26,7 @@ import com.android.internal.infra.AndroidFuture;
|
||||
|
||||
import com.google.android.icing.proto.SchemaProto;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -95,4 +97,34 @@ public class AppSearchManager {
|
||||
}
|
||||
future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index {@link Document} to AppSearch
|
||||
*
|
||||
* <p>You should not call this method directly; instead, use the {@code AppSearch#put()} API
|
||||
* provided by JetPack.
|
||||
*
|
||||
* <p>The schema should be set via {@link #setSchema} method.
|
||||
*
|
||||
* @param documents {@link Document Documents} that need to be indexed.
|
||||
* @param executor Executor on which to invoke the callback.
|
||||
* @param callback Callback to receive errors resulting from setting the schema. If the
|
||||
* operation succeeds, the callback will be invoked with {@code null}.
|
||||
*/
|
||||
public void put(@NonNull List<Document> documents,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull Consumer<? super Throwable> callback) {
|
||||
AndroidFuture<Void> future = new AndroidFuture<>();
|
||||
for (Document document : documents) {
|
||||
// TODO(b/146386470) batching Document protos
|
||||
try {
|
||||
mService.put(document.getProto().toByteArray(), future);
|
||||
} catch (RemoteException e) {
|
||||
future.completeExceptionally(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO(b/147614371) Fix error report for multiple documents.
|
||||
future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,5 @@ interface IAppSearchManager {
|
||||
* if setSchema fails.
|
||||
*/
|
||||
void setSchema(in byte[] schemaProto, in AndroidFuture callback);
|
||||
void put(in byte[] documentBytes, in AndroidFuture callback);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,16 @@ public class AppSearchManagerService extends SystemService {
|
||||
try {
|
||||
SchemaProto schema = SchemaProto.parseFrom(schemaBytes);
|
||||
throw new UnsupportedOperationException("setSchema not yet implemented: " + schema);
|
||||
|
||||
} catch (Throwable t) {
|
||||
callback.completeExceptionally(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(byte[] documentBytes, AndroidFuture callback) {
|
||||
try {
|
||||
throw new UnsupportedOperationException("Put document not yet implemented");
|
||||
} catch (Throwable t) {
|
||||
callback.completeExceptionally(t);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,14 @@
|
||||
"include-filter": "com.android.server.appsearch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FrameworksCoreTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "android.app.appsearch"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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 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 org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@SmallTest
|
||||
public class AppSearchDocumentTest {
|
||||
|
||||
@Test
|
||||
public void testDocumentEquals_Identical() {
|
||||
Document document1 = Document.newBuilder("uri1", "schemaType1")
|
||||
.setCreationTimestampSecs(0L)
|
||||
.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")
|
||||
.build();
|
||||
Document document2 = Document.newBuilder("uri1", "schemaType1")
|
||||
.setCreationTimestampSecs(0L)
|
||||
.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")
|
||||
.build();
|
||||
assertThat(document1).isEqualTo(document2);
|
||||
assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentEquals_DifferentOrder() {
|
||||
Document document1 = Document.newBuilder("uri1", "schemaType1")
|
||||
.setCreationTimestampSecs(0L)
|
||||
.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")
|
||||
.build();
|
||||
|
||||
// Create second document with same parameter but different order.
|
||||
Document document2 = Document.newBuilder("uri1", "schemaType1")
|
||||
.setCreationTimestampSecs(0L)
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
|
||||
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
|
||||
.setProperty("longKey1", 1L, 2L, 3L)
|
||||
.build();
|
||||
assertThat(document1).isEqualTo(document2);
|
||||
assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentEquals_Failure() {
|
||||
Document document1 = Document.newBuilder("uri1", "schemaType1")
|
||||
.setProperty("longKey1", 1L, 2L, 3L)
|
||||
.build();
|
||||
|
||||
// Create second document with same order but different value.
|
||||
Document document2 = Document.newBuilder("uri1", "schemaType1")
|
||||
.setProperty("longKey1", 1L, 2L, 4L) // Different
|
||||
.build();
|
||||
assertThat(document1).isNotEqualTo(document2);
|
||||
assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentEquals_Failure_RepeatedFieldOrder() {
|
||||
Document document1 = Document.newBuilder("uri1", "schemaType1")
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.build();
|
||||
|
||||
// Create second document with same order but different value.
|
||||
Document document2 = Document.newBuilder("uri1", "schemaType1")
|
||||
.setProperty("booleanKey1", true, true, false) // Different
|
||||
.build();
|
||||
assertThat(document1).isNotEqualTo(document2);
|
||||
assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentGetSingleValue() {
|
||||
Document document = Document.newBuilder("uri1", "schemaType1")
|
||||
.setProperty("longKey1", 1L)
|
||||
.setProperty("doubleKey1", 1.0)
|
||||
.setProperty("booleanKey1", true)
|
||||
.setProperty("stringKey1", "test-value1").build();
|
||||
assertThat(document.getUri()).isEqualTo("uri1");
|
||||
assertThat(document.getSchemaType()).isEqualTo("schemaType1");
|
||||
assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
|
||||
assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
|
||||
assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
|
||||
assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentGetArrayValues() {
|
||||
Document document = Document.newBuilder("uri1", "schemaType1")
|
||||
.setScore(1)
|
||||
.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")
|
||||
.build();
|
||||
|
||||
assertThat(document.getUri()).isEqualTo("uri1");
|
||||
assertThat(document.getSchemaType()).isEqualTo("schemaType1");
|
||||
assertThat(document.getScore()).isEqualTo(1);
|
||||
assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L);
|
||||
assertThat(document.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
|
||||
.containsExactly(1.0, 2.0, 3.0);
|
||||
assertThat(document.getPropertyBooleanArray("booleanKey1")).asList()
|
||||
.containsExactly(true, false, true);
|
||||
assertThat(document.getPropertyStringArray("stringKey1")).asList()
|
||||
.containsExactly("test-value1", "test-value2", "test-value3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentGetValues_DifferentTypes() {
|
||||
Document document = Document.newBuilder("uri1", "schemaType1")
|
||||
.setScore(1)
|
||||
.setProperty("longKey1", 1L)
|
||||
.setProperty("booleanKey1", true, false, true)
|
||||
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
|
||||
.build();
|
||||
|
||||
// Get a value for a key that doesn't exist
|
||||
assertThat(document.getPropertyDouble("doubleKey1")).isNull();
|
||||
assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull();
|
||||
|
||||
// Get a value with a single element as an array and as a single value
|
||||
assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
|
||||
assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L);
|
||||
|
||||
// Get a value with multiple elements as an array and as a single value
|
||||
assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
|
||||
assertThat(document.getPropertyStringArray("stringKey1")).asList()
|
||||
.containsExactly("test-value1", "test-value2", "test-value3");
|
||||
|
||||
// Get a value of the wrong type
|
||||
assertThat(document.getPropertyDouble("longKey1")).isNull();
|
||||
assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentInvalid() {
|
||||
Document.Builder builder = Document.newBuilder("uri1", "schemaType1");
|
||||
assertThrows(
|
||||
IllegalArgumentException.class, () -> builder.setProperty("test", new boolean[]{}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocumentProtoPopulation() {
|
||||
Document document = Document.newBuilder("uri1", "schemaType1")
|
||||
.setScore(1)
|
||||
.setCreationTimestampSecs(0)
|
||||
.setProperty("longKey1", 1L)
|
||||
.setProperty("doubleKey1", 1.0)
|
||||
.setProperty("booleanKey1", true)
|
||||
.setProperty("stringKey1", "test-value1")
|
||||
.build();
|
||||
|
||||
// Create the Document proto. Need to sort the property order by key.
|
||||
DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
|
||||
.setUri("uri1").setSchema("schemaType1").setScore(1).setCreationTimestampSecs(0);
|
||||
HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
|
||||
propertyProtoMap.put("longKey1",
|
||||
PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
|
||||
propertyProtoMap.put("doubleKey1",
|
||||
PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0));
|
||||
propertyProtoMap.put("booleanKey1",
|
||||
PropertyProto.newBuilder().setName("booleanKey1").addBooleanValues(true));
|
||||
propertyProtoMap.put("stringKey1",
|
||||
PropertyProto.newBuilder().setName("stringKey1").addStringValues("test-value1"));
|
||||
List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet());
|
||||
Collections.sort(sortedKey);
|
||||
for (String key : sortedKey) {
|
||||
documentProtoBuilder.addProperties(propertyProtoMap.get(key));
|
||||
}
|
||||
assertThat(document.getProto()).isEqualTo(documentProtoBuilder.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.appsearch.AppSearch.Email;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@SmallTest
|
||||
public class AppSearchEmailTest {
|
||||
|
||||
@Test
|
||||
public void testBuildEmailAndGetValue() {
|
||||
Email email = Email.newBuilder("uri")
|
||||
.setFrom("FakeFromAddress")
|
||||
.setCc("CC1", "CC2")
|
||||
// Score and Property are mixed into the middle to make sure DocumentBuilder's
|
||||
// methods can be interleaved with EmailBuilder's methods.
|
||||
.setScore(1)
|
||||
.setProperty("propertyKey", "propertyValue1", "propertyValue2")
|
||||
.setSubject("subject")
|
||||
.setBody("EmailBody")
|
||||
.build();
|
||||
|
||||
assertThat(email.getUri()).isEqualTo("uri");
|
||||
assertThat(email.getFrom()).isEqualTo("FakeFromAddress");
|
||||
assertThat(email.getTo()).isNull();
|
||||
assertThat(email.getCc()).asList().containsExactly("CC1", "CC2");
|
||||
assertThat(email.getBcc()).isNull();
|
||||
assertThat(email.getScore()).isEqualTo(1);
|
||||
assertThat(email.getPropertyString("propertyKey")).isEqualTo("propertyValue1");
|
||||
assertThat(email.getPropertyStringArray("propertyKey")).asList().containsExactly(
|
||||
"propertyValue1", "propertyValue2");
|
||||
assertThat(email.getSubject()).isEqualTo("subject");
|
||||
assertThat(email.getBody()).isEqualTo("EmailBody");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.impl;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.appsearch.AppSearch.Document;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/** Tests that {@link Document} and {@link Document.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.
|
||||
*/
|
||||
@SmallTest
|
||||
public class CustomerDocumentTest {
|
||||
@Test
|
||||
public void testBuildCustomerDocument() {
|
||||
CustomerDocument customerDocument = CustomerDocument.newBuilder("uri1")
|
||||
.setScore(1)
|
||||
.setCreationTimestampSecs(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")
|
||||
.build();
|
||||
|
||||
assertThat(customerDocument.getUri()).isEqualTo("uri1");
|
||||
assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
|
||||
assertThat(customerDocument.getScore()).isEqualTo(1);
|
||||
assertThat(customerDocument.getCreationTimestampSecs()).isEqualTo(0L);
|
||||
assertThat(customerDocument.getPropertyLongArray("longKey1")).asList()
|
||||
.containsExactly(1L, 2L, 3L);
|
||||
assertThat(customerDocument.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
|
||||
.containsExactly(1.0, 2.0, 3.0);
|
||||
assertThat(customerDocument.getPropertyBooleanArray("booleanKey1")).asList()
|
||||
.containsExactly(true, false, true);
|
||||
assertThat(customerDocument.getPropertyStringArray("stringKey1")).asList()
|
||||
.containsExactly("test-value1", "test-value2", "test-value3");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
super(document);
|
||||
}
|
||||
|
||||
public static CustomerDocument.Builder newBuilder(String uri) {
|
||||
return new CustomerDocument.Builder(uri);
|
||||
}
|
||||
|
||||
public static class Builder extends Document.Builder<CustomerDocument.Builder> {
|
||||
private Builder(@NonNull String uri) {
|
||||
super(uri, "customerDocument");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomerDocument build() {
|
||||
return new CustomerDocument(super.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user