Merge "Implement putDocuments() support in AppSearchImpl."

This commit is contained in:
Alexander Dorokhine
2020-01-19 19:33:53 +00:00
committed by Android (Google) Code Review
6 changed files with 169 additions and 40 deletions

View File

@@ -20,6 +20,7 @@ import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchSchema.PropertyConfig;
import android.os.Bundle;
import android.util.Log;
@@ -574,10 +575,6 @@ public final class AppSearch {
* @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";
@@ -585,14 +582,53 @@ public final class AppSearch {
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);
}
/** The name of the schema type for {@link Email} documents.*/
public static final String SCHEMA_TYPE = "builtin:Email";
public static final AppSearchSchema SCHEMA = AppSearchSchema.newBuilder(SCHEMA_TYPE)
.addProperty(AppSearchSchema.newPropertyBuilder(KEY_FROM)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
).addProperty(AppSearchSchema.newPropertyBuilder(KEY_TO)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
).addProperty(AppSearchSchema.newPropertyBuilder(KEY_CC)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
).addProperty(AppSearchSchema.newPropertyBuilder(KEY_BCC)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_REPEATED)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
).addProperty(AppSearchSchema.newPropertyBuilder(KEY_SUBJECT)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
).addProperty(AppSearchSchema.newPropertyBuilder(KEY_BODY)
.setDataType(PropertyConfig.DATA_TYPE_STRING)
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.build()
).build();
/**
* Creates a new {@link Email.Builder}.
@@ -603,6 +639,15 @@ public final class AppSearch {
return new Builder(uri);
}
/**
* Creates a new {@link Email} from the contents of an existing {@link Document}.
*
* @param document The {@link Document} containing the email content.
*/
public Email(@NonNull Document document) {
super(document);
}
/**
* Get the from address of {@link Email}.
*
@@ -615,10 +660,10 @@ public final class AppSearch {
}
/**
* Get the destination address of {@link Email}.
* Get the destination addresses of {@link Email}.
*
* @return Returns the destination address of {@link Email} or {@code null} if it's not been
* set yet.
* @return Returns the destination addresses of {@link Email} or {@code null} if it's not
* been set yet.
* @hide
*/
@Nullable

View File

@@ -54,7 +54,7 @@ public class AppSearchManager {
}
/**
* Sets the schema being used by documents provided to the #put method.
* Sets the schema being used by documents provided to the {@link #putDocuments} method.
*
* <p>The schema provided here is compared to the stored copy of the schema previously supplied
* to {@link #setSchema}, if any, to determine how to treat existing documents. The following
@@ -106,13 +106,12 @@ public class AppSearchManager {
*
* @hide
*/
// TODO(b/143789408): linkify #put after that API is created
public void setSchema(@NonNull AppSearchSchema... schemas) {
setSchema(Arrays.asList(schemas), /*forceOverride=*/false);
}
/**
* Sets the schema being used by documents provided to the #put method.
* Sets the schema being used by documents provided to the {@link #putDocuments} method.
*
* <p>This method is similar to {@link #setSchema(AppSearchSchema...)}, except for the
* {@code forceOverride} parameter. If a backwards-incompatible schema is specified but the
@@ -129,7 +128,6 @@ public class AppSearchManager {
*
* @hide
*/
// TODO(b/143789408): linkify #put after that API is created
public void setSchema(@NonNull List<AppSearchSchema> schemas, boolean forceOverride) {
// Prepare the merged schema for transmission.
SchemaProto.Builder schemaProtoBuilder = SchemaProto.newBuilder();
@@ -151,26 +149,28 @@ public class AppSearchManager {
}
/**
* Index {@link Document} to AppSearch
* Index {@link android.app.appsearch.AppSearch.Document Documents} into AppSearch.
*
* <p>You should not call this method directly; instead, use the {@code AppSearch#put()} API
* provided by JetPack.
* <p>You should not call this method directly; instead, use the
* {@code AppSearch#putDocuments()} API provided by JetPack.
*
* <p>The schema should be set via {@link #setSchema} method.
* <p>Each {@link AppSearch.Document Document's} {@code schemaType} field must be set to the
* name of a schema type previously registered via the {@link #setSchema} method.
*
* @param documents {@link Document Documents} that need to be indexed.
* @param 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}.
* @param callback Callback to receive errors. On success, it will be called with {@code null}.
* On failure, it will be called with a {@link Throwable} describing the failure.
*/
public void put(@NonNull List<Document> documents,
public void putDocuments(
@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);
mService.putDocument(document.getProto().toByteArray(), future);
} catch (RemoteException e) {
future.completeExceptionally(e);
break;

View File

@@ -22,15 +22,25 @@ interface IAppSearchManager {
/**
* Sets the schema.
*
* @param schemaProto Serialized SchemaProto.
* @param schemaBytes Serialized SchemaProto.
* @param forceOverride Whether to apply the new schema even if it is incompatible. All
* incompatible documents will be deleted.
* @param callback {@link AndroidFuture}&lt;{@link Void}&gt;. Will be completed with
* {@code null} upon successful completion of the setSchema call, or completed
* exceptionally if setSchema fails.
*/
void setSchema(in byte[] schemaProto, boolean forceOverride, in AndroidFuture callback);
void put(in byte[] documentBytes, in AndroidFuture callback);
void setSchema(in byte[] schemaBytes, boolean forceOverride, in AndroidFuture callback);
/**
* Inserts a document into the index.
*
* @param documentBytes serialized DocumentProto
* @param callback {@link AndroidFuture}&lt;{@link Void}&gt;. Will be completed with
* {@code null} upon successful completion of the put call, or completed exceptionally if
* put fails.
*/
void putDocument(in byte[] documentBytes, in AndroidFuture callback);
/**
* Searches a document based on a given query string.
*

View File

@@ -28,6 +28,7 @@ import com.android.server.appsearch.impl.AppSearchImpl;
import com.android.server.appsearch.impl.FakeIcing;
import com.android.server.appsearch.impl.ImplInstanceManager;
import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.SchemaProto;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchSpecProto;
@@ -71,11 +72,21 @@ public class AppSearchManagerService extends SystemService {
}
@Override
public void put(byte[] documentBytes, AndroidFuture callback) {
public void putDocument(byte[] documentBytes, AndroidFuture callback) {
Preconditions.checkNotNull(documentBytes);
Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
long callingIdentity = Binder.clearCallingIdentity();
try {
throw new UnsupportedOperationException("Put document not yet implemented");
DocumentProto document = DocumentProto.parseFrom(documentBytes);
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
impl.putDocument(callingUid, document);
callback.complete(null);
} catch (Throwable t) {
callback.completeExceptionally(t);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
}
// TODO(sidchhabra):Init FakeIcing properly.

View File

@@ -22,7 +22,9 @@ import android.content.Context;
import com.android.internal.annotations.VisibleForTesting;
import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.PropertyConfigProto;
import com.google.android.icing.proto.PropertyProto;
import com.google.android.icing.proto.SchemaProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;
@@ -95,6 +97,60 @@ public final class AppSearchImpl {
}
/**
* Adds a document to the AppSearch index.
*
* @param callingUid The uid of the app calling AppSearch.
* @param origDocument The document to index.
*/
public void putDocument(int callingUid, @NonNull DocumentProto origDocument) {
// Rewrite the type names to include the app's prefix
String typePrefix = getTypePrefix(callingUid);
DocumentProto.Builder documentBuilder = origDocument.toBuilder();
rewriteDocumentTypes(typePrefix, documentBuilder);
mFakeIcing.put(documentBuilder.build());
}
/**
* Rewrites all types mentioned anywhere in {@code documentBuilder} to prepend
* {@code typePrefix}.
*
* @param typePrefix The prefix to add
* @param documentBuilder The document to mutate
*/
@VisibleForTesting
void rewriteDocumentTypes(
@NonNull String typePrefix,
@NonNull DocumentProto.Builder documentBuilder) {
// Rewrite the type name to include the app's prefix
String newSchema = typePrefix + documentBuilder.getSchema();
documentBuilder.setSchema(newSchema);
// Add namespace. If we ever allow users to set their own namespaces, this will have
// to change to prepend the prefix instead of setting the whole namespace. We will also have
// to store the namespaces in a map similar to the type map so we can rewrite queries with
// empty namespaces.
documentBuilder.setNamespace(typePrefix);
// Recurse into derived documents
for (int propertyIdx = 0;
propertyIdx < documentBuilder.getPropertiesCount();
propertyIdx++) {
int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
if (documentCount > 0) {
PropertyProto.Builder propertyBuilder =
documentBuilder.getProperties(propertyIdx).toBuilder();
for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
DocumentProto.Builder derivedDocumentBuilder =
propertyBuilder.getDocumentValues(documentIdx).toBuilder();
rewriteDocumentTypes(typePrefix, derivedDocumentBuilder);
propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
}
documentBuilder.setProperties(propertyIdx, propertyBuilder);
}
}
}
/**
* Returns a type prefix in a format like {@code com.example.package@1000/} or
* {@code com.example.sharedname:5678@1000/}.
*/

View File

@@ -40,14 +40,14 @@ public class AppSearchDocumentTest {
@Test
public void testDocumentEquals_Identical() {
Document document1 = Document.newBuilder("uri1", "schemaType1")
.setCreationTimestampMillis(0L)
.setCreationTimestampMillis(5L)
.setProperty("longKey1", 1L, 2L, 3L)
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
.setProperty("booleanKey1", true, false, true)
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
.build();
Document document2 = Document.newBuilder("uri1", "schemaType1")
.setCreationTimestampMillis(0L)
.setCreationTimestampMillis(5L)
.setProperty("longKey1", 1L, 2L, 3L)
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
.setProperty("booleanKey1", true, false, true)
@@ -60,7 +60,7 @@ public class AppSearchDocumentTest {
@Test
public void testDocumentEquals_DifferentOrder() {
Document document1 = Document.newBuilder("uri1", "schemaType1")
.setCreationTimestampMillis(0L)
.setCreationTimestampMillis(5L)
.setProperty("longKey1", 1L, 2L, 3L)
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
.setProperty("booleanKey1", true, false, true)
@@ -69,7 +69,7 @@ public class AppSearchDocumentTest {
// Create second document with same parameter but different order.
Document document2 = Document.newBuilder("uri1", "schemaType1")
.setCreationTimestampMillis(0L)
.setCreationTimestampMillis(5L)
.setProperty("booleanKey1", true, false, true)
.setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
@@ -82,11 +82,13 @@ public class AppSearchDocumentTest {
@Test
public void testDocumentEquals_Failure() {
Document document1 = Document.newBuilder("uri1", "schemaType1")
.setCreationTimestampMillis(5L)
.setProperty("longKey1", 1L, 2L, 3L)
.build();
// Create second document with same order but different value.
Document document2 = Document.newBuilder("uri1", "schemaType1")
.setCreationTimestampMillis(5L)
.setProperty("longKey1", 1L, 2L, 4L) // Different
.build();
assertThat(document1).isNotEqualTo(document2);
@@ -96,11 +98,13 @@ public class AppSearchDocumentTest {
@Test
public void testDocumentEquals_Failure_RepeatedFieldOrder() {
Document document1 = Document.newBuilder("uri1", "schemaType1")
.setCreationTimestampMillis(5L)
.setProperty("booleanKey1", true, false, true)
.build();
// Create second document with same order but different value.
Document document2 = Document.newBuilder("uri1", "schemaType1")
.setCreationTimestampMillis(5L)
.setProperty("booleanKey1", true, true, false) // Different
.build();
assertThat(document1).isNotEqualTo(document2);
@@ -110,12 +114,16 @@ public class AppSearchDocumentTest {
@Test
public void testDocumentGetSingleValue() {
Document document = Document.newBuilder("uri1", "schemaType1")
.setCreationTimestampMillis(5L)
.setScore(1)
.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.getCreationTimestampMillis()).isEqualTo(5);
assertThat(document.getScore()).isEqualTo(1);
assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
@@ -125,7 +133,7 @@ public class AppSearchDocumentTest {
@Test
public void testDocumentGetArrayValues() {
Document document = Document.newBuilder("uri1", "schemaType1")
.setScore(1)
.setCreationTimestampMillis(5L)
.setProperty("longKey1", 1L, 2L, 3L)
.setProperty("doubleKey1", 1.0, 2.0, 3.0)
.setProperty("booleanKey1", true, false, true)
@@ -134,7 +142,6 @@ public class AppSearchDocumentTest {
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);
@@ -181,8 +188,8 @@ public class AppSearchDocumentTest {
@Test
public void testDocumentProtoPopulation() {
Document document = Document.newBuilder("uri1", "schemaType1")
.setCreationTimestampMillis(5L)
.setScore(1)
.setCreationTimestampMillis(0)
.setProperty("longKey1", 1L)
.setProperty("doubleKey1", 1.0)
.setProperty("booleanKey1", true)
@@ -191,7 +198,7 @@ public class AppSearchDocumentTest {
// Create the Document proto. Need to sort the property order by key.
DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
.setUri("uri1").setSchema("schemaType1").setScore(1).setCreationTimestampMs(0);
.setUri("uri1").setSchema("schemaType1").setScore(1).setCreationTimestampMs(5L);
HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
propertyProtoMap.put("longKey1",
PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));