Merge "Implement putDocuments() support in AppSearchImpl."
This commit is contained in:
committed by
Android (Google) Code Review
commit
0231ac864b
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}<{@link Void}>. 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}<{@link Void}>. 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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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/}.
|
||||
*/
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user