diff options
6 files changed, 169 insertions, 40 deletions
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java index 5b412499ee15..fd201866e702 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java @@ -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}. @@ -604,6 +640,15 @@ public final class AppSearch { } /** + * Creates a new {@link Email} from the contents of an existing {@link Document}. + * + * @param document The {@link Document} containing the email content. + */ + public Email(@NonNull Document document) { + super(document); + } + + /** * Get the from address of {@link Email}. * * @return Returns the subject of {@link Email} or {@code null} if it's not been set yet. @@ -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 diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index 33f69a456fb2..66cba52ae03a 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -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; diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index 194e43e36e4d..c9c5d7fbfda4 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -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. * 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 5d6d3f0a8406..6929202445e9 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -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. diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java index 177c9108b506..04b4b1427328 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java @@ -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/}. */ diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java index 9e4440a1d57d..abba7fcce6c7 100644 --- a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java @@ -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)); |