Add a SetSchema API and builders.

They directly construct the proto in frameworks.jar.

The API is not yet implemented.

Test: atest CtsAppSearchTestCases
Bug: 145635424
Change-Id: I4f708fa845a87f171ace437788d2c45161fc38ff
This commit is contained in:
Alexander Dorokhine 2020-01-13 20:22:20 -08:00
parent 2ba07e09cf
commit 0e8fa4e65b
9 changed files with 587 additions and 42 deletions

View File

@ -509,7 +509,8 @@ java_library {
static_libs: [
"exoplayer2-core",
"android.hardware.wifi-V1.0-java-constants",
],
],
libs: ["icing-java-proto-lite"],
apex_available: ["//apex_available:platform"],
}

View File

@ -13,29 +13,32 @@
// limitations under the License.
filegroup {
name: "framework-appsearch-sources",
srcs: [
"java/**/*.java",
"java/**/*.aidl",
],
path: "java",
name: "framework-appsearch-sources",
srcs: [
"java/**/*.java",
"java/**/*.aidl",
],
path: "java",
}
java_library {
name: "framework-appsearch",
installable: true,
sdk_version: "core_platform", // TODO(b/146218515) should be core_current
srcs: [":framework-appsearch-sources"],
hostdex: true, // for hiddenapi check
libs: [
"framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
],
visibility: [
"//frameworks/base/apex/appsearch:__subpackages__",
// TODO(b/146218515) remove this when framework is built with the stub of appsearch
"//frameworks/base",
],
apex_available: ["com.android.appsearch"],
name: "framework-appsearch",
installable: true,
sdk_version: "core_platform", // TODO(b/146218515) should be core_current
srcs: [":framework-appsearch-sources"],
hostdex: true, // for hiddenapi check
libs: [
"framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
],
static_libs: [
"icing-java-proto-lite",
],
visibility: [
"//frameworks/base/apex/appsearch:__subpackages__",
// TODO(b/146218515) remove this when framework is built with the stub of appsearch
"//frameworks/base",
],
apex_available: ["com.android.appsearch"],
}
metalava_appsearch_docs_args =

View File

@ -15,21 +15,84 @@
*/
package android.app.appsearch;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
import com.android.internal.infra.AndroidFuture;
import com.google.android.icing.proto.SchemaProto;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* TODO(b/142567528): add comments when implement this class
* This class provides access to the centralized AppSearch index maintained by the system.
*
* <p>Apps can index structured text documents with AppSearch, which can then be retrieved through
* the query API.
*
* @hide
*/
@SystemService(Context.APP_SEARCH_SERVICE)
public class AppSearchManager {
private final IAppSearchManager mService;
/**
* TODO(b/142567528): add comments when implement this class
* @hide
*/
public AppSearchManager(IAppSearchManager service) {
/** @hide */
public AppSearchManager(@NonNull IAppSearchManager service) {
mService = service;
}
/**
* Sets the schema being used by documents provided to the #put method.
*
* <p>This operation is performed asynchronously. On success, the provided callback will be
* called with {@code null}. On failure, the provided callback will be called with a
* {@link Throwable} describing the failure.
*
* <p>It is a no-op to set the same schema as has been previously set; this is handled
* efficiently.
*
* <p>AppSearch automatically handles the following types of schema changes:
* <ul>
* <li>Addition of new types (No changes to storage or index)
* <li>Removal of an existing type (All documents of the removed type are deleted)
* <li>Addition of new 'optional' property to a type (No changes to storage or index)
* <li>Removal of existing property of any cardinality (All documents reindexed)
* </ul>
*
* <p>This method will return an error when attempting to make the following types of changes:
* <ul>
* <li>Changing the type of an existing property
* <li>Adding a 'required' property
* </ul>
*
* @param schema The schema config for this app.
* @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}.
*
* @hide
*/
// TODO(b/143789408): linkify #put after that API is created
// TODO(b/145635424): add a 'force' param to setSchema after the corresponding API is finalized
// in Icing Library
// TODO(b/145635424): Update the documentation above once the Schema mutation APIs of Icing
// Library are finalized
public void setSchema(
@NonNull AppSearchSchema schema,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<? super Throwable> callback) {
SchemaProto schemaProto = schema.getProto();
byte[] schemaBytes = schemaProto.toByteArray();
AndroidFuture<Void> future = new AndroidFuture<>();
try {
mService.setSchema(schemaBytes, future);
} catch (RemoteException e) {
future.completeExceptionally(e);
}
future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
}
}

View File

@ -0,0 +1,423 @@
/*
* Copyright (C) 2019 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.IntDef;
import android.annotation.NonNull;
import com.google.android.icing.proto.PropertyConfigProto;
import com.google.android.icing.proto.SchemaProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Representation of the AppSearch Schema.
*
* <p>The schema is the set of document types, properties, and config (like tokenization type)
* understood by AppSearch for this app.
*
* @hide
*/
public final class AppSearchSchema {
private final SchemaProto mProto;
private AppSearchSchema(SchemaProto proto) {
mProto = proto;
}
/** Creates a new {@link AppSearchSchema.Builder}. */
@NonNull
public static AppSearchSchema.Builder newBuilder() {
return new AppSearchSchema.Builder();
}
/** Creates a new {@link SchemaType.Builder}. */
@NonNull
public static SchemaType.Builder newSchemaTypeBuilder(@NonNull String typeName) {
return new SchemaType.Builder(typeName);
}
/** Creates a new {@link PropertyConfig.Builder}. */
@NonNull
public static PropertyConfig.Builder newPropertyBuilder(@NonNull String propertyName) {
return new PropertyConfig.Builder(propertyName);
}
/** Creates a new {@link IndexingConfig.Builder}. */
@NonNull
public static IndexingConfig.Builder newIndexingConfigBuilder() {
return new IndexingConfig.Builder();
}
/**
* Returns the schema proto populated by the {@link AppSearchSchema} builders.
* @hide
*/
@NonNull
SchemaProto getProto() {
return mProto;
}
/** Builder for {@link AppSearchSchema objects}. */
public static final class Builder {
private final SchemaProto.Builder mProtoBuilder = SchemaProto.newBuilder();
private Builder() {}
/** Adds a supported type to this app's AppSearch schema. */
@NonNull
public AppSearchSchema.Builder addType(@NonNull SchemaType schemaType) {
mProtoBuilder.addTypes(schemaType.mProto);
return this;
}
/**
* Constructs a new {@link AppSearchSchema} from the contents of this builder.
*
* <p>After calling this method, the builder must no longer be used.
*/
@NonNull
public AppSearchSchema build() {
return new AppSearchSchema(mProtoBuilder.build());
}
}
/**
* Represents a type of a document.
*
* <p>For example, an e-mail message or a music recording could be a schema type.
*/
public static final class SchemaType {
private final SchemaTypeConfigProto mProto;
private SchemaType(SchemaTypeConfigProto proto) {
mProto = proto;
}
/** Builder for {@link SchemaType} objects. */
public static final class Builder {
private final SchemaTypeConfigProto.Builder mProtoBuilder =
SchemaTypeConfigProto.newBuilder();
private Builder(@NonNull String typeName) {
mProtoBuilder.setSchemaType(typeName);
}
/** Adds a property to the given type. */
@NonNull
public SchemaType.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
mProtoBuilder.addProperties(propertyConfig.mProto);
return this;
}
/**
* Constructs a new {@link SchemaType} from the contents of this builder.
*
* <p>After calling this method, the builder must no longer be used.
*/
@NonNull
public SchemaType build() {
return new SchemaType(mProtoBuilder.build());
}
}
}
/**
* Configuration for a single property (field) of a document type.
*
* <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be
* a property.
*/
public static final class PropertyConfig {
/** Physical data-types of the contents of the property. */
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
@IntDef(prefix = {"DATA_TYPE_"}, value = {
DATA_TYPE_STRING,
DATA_TYPE_INT64,
DATA_TYPE_DOUBLE,
DATA_TYPE_BOOLEAN,
DATA_TYPE_BYTES,
DATA_TYPE_DOCUMENT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DataType {}
public static final int DATA_TYPE_STRING = 1;
public static final int DATA_TYPE_INT64 = 2;
public static final int DATA_TYPE_DOUBLE = 3;
public static final int DATA_TYPE_BOOLEAN = 4;
/** Unstructured BLOB. */
public static final int DATA_TYPE_BYTES = 5;
/**
* Indicates that the property itself is an Document, making it part a hierarchical
* Document schema. Any property using this DataType MUST have a valid
* {@code schemaType}.
*/
public static final int DATA_TYPE_DOCUMENT = 6;
/** The cardinality of the property (whether it is required, optional or repeated). */
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
@IntDef(prefix = {"CARDINALITY_"}, value = {
CARDINALITY_REPEATED,
CARDINALITY_OPTIONAL,
CARDINALITY_REQUIRED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Cardinality {}
/** Any number of items (including zero) [0...*]. */
public static final int CARDINALITY_REPEATED = 1;
/** Zero or one value [0,1]. */
public static final int CARDINALITY_OPTIONAL = 2;
/** Exactly one value [1]. */
public static final int CARDINALITY_REQUIRED = 3;
private final PropertyConfigProto mProto;
private PropertyConfig(PropertyConfigProto proto) {
mProto = proto;
}
/**
* Builder for {@link PropertyConfig}.
*
* <p>The following properties must be set, or {@link PropertyConfig} construction will
* fail:
* <ul>
* <li>dataType
* <li>cardinality
* </ul>
*
* <p>In addition, if {@code schemaType} is {@link #DATA_TYPE_DOCUMENT}, {@code schemaType}
* is also required.
*/
public static final class Builder {
private final PropertyConfigProto.Builder mProtoBuilder =
PropertyConfigProto.newBuilder();
private Builder(String propertyName) {
mProtoBuilder.setPropertyName(propertyName);
}
/**
* Type of data the property contains (e.g. string, int, bytes, etc).
*
* <p>This property must be set.
*/
@NonNull
public PropertyConfig.Builder setDataType(@DataType int dataType) {
PropertyConfigProto.DataType.Code dataTypeProto =
PropertyConfigProto.DataType.Code.forNumber(dataType);
if (dataTypeProto == null) {
throw new IllegalArgumentException("Invalid dataType: " + dataType);
}
mProtoBuilder.setDataType(dataTypeProto);
return this;
}
/**
* The logical schema-type of the contents of this property.
*
* <p>Only required when {@link #setDataType(int)} is set to
* {@link #DATA_TYPE_DOCUMENT}. Otherwise, it is ignored.
*/
@NonNull
public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
mProtoBuilder.setSchemaType(schemaType);
return this;
}
/**
* The cardinality of the property (whether it is optional, required or repeated).
*
* <p>This property must be set.
*/
@NonNull
public PropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
PropertyConfigProto.Cardinality.Code cardinalityProto =
PropertyConfigProto.Cardinality.Code.forNumber(cardinality);
if (cardinalityProto == null) {
throw new IllegalArgumentException("Invalid cardinality: " + cardinality);
}
mProtoBuilder.setCardinality(cardinalityProto);
return this;
}
/**
* Configures how this property should be indexed.
*
* <p>If this is not supplied, the property will not be indexed at all.
*/
@NonNull
public PropertyConfig.Builder setIndexingConfig(
@NonNull IndexingConfig indexingConfig) {
mProtoBuilder.setIndexingConfig(indexingConfig.mProto);
return this;
}
/**
* Constructs a new {@link PropertyConfig} from the contents of this builder.
*
* <p>After calling this method, the builder must no longer be used.
*
* @throws IllegalSchemaException If the property is not correctly populated (e.g.
* missing {@code dataType}).
*/
@NonNull
public PropertyConfig build() {
if (mProtoBuilder.getDataType() == PropertyConfigProto.DataType.Code.UNKNOWN) {
throw new IllegalSchemaException("Missing dataType field");
}
if (mProtoBuilder.getSchemaType().isEmpty()
&& mProtoBuilder.getDataType()
== PropertyConfigProto.DataType.Code.DOCUMENT) {
throw new IllegalSchemaException(
"Missing field: schemaType (required for configs with "
+ "dataType = DOCUMENT)");
}
if (mProtoBuilder.getCardinality()
== PropertyConfigProto.Cardinality.Code.UNKNOWN) {
throw new IllegalSchemaException("Missing cardinality field");
}
return new PropertyConfig(mProtoBuilder.build());
}
}
}
/** Configures how a property should be indexed so that it can be retrieved by queries. */
public static final class IndexingConfig {
/** Encapsulates the configurations on how AppSearch should query/index these terms. */
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.TermMatchType.Code.
@IntDef(prefix = {"TERM_MATCH_TYPE_"}, value = {
TERM_MATCH_TYPE_UNKNOWN,
TERM_MATCH_TYPE_EXACT_ONLY,
TERM_MATCH_TYPE_PREFIX,
})
@Retention(RetentionPolicy.SOURCE)
public @interface TermMatchType {}
/**
* Content in this property will not be tokenized or indexed.
*
* <p>Useful if the data type is not made up of terms (e.g.
* {@link PropertyConfig#DATA_TYPE_DOCUMENT} or {@link PropertyConfig#DATA_TYPE_BYTES}
* type). All the properties inside the nested property won't be indexed regardless of the
* value of {@code termMatchType} for the nested properties.
*/
public static final int TERM_MATCH_TYPE_UNKNOWN = 0;
/**
* Content in this property should only be returned for queries matching the exact tokens
* appearing in this property.
*
* <p>Ex. A property with "fool" should NOT match a query for "foo".
*/
public static final int TERM_MATCH_TYPE_EXACT_ONLY = 1;
/**
* Content in this property should be returned for queries that are either exact matches or
* query matches of the tokens appearing in this property.
*
* <p>Ex. A property with "fool" <b>should</b> match a query for "foo".
*/
public static final int TERM_MATCH_TYPE_PREFIX = 2;
/** Configures how tokens should be extracted from this property. */
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
@IntDef(prefix = {"TOKENIZER_TYPE_"}, value = {
TOKENIZER_TYPE_NONE,
TOKENIZER_TYPE_PLAIN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface TokenizerType {}
/**
* It is only valid for tokenizer_type to be 'NONE' if the data type is
* {@link PropertyConfig#DATA_TYPE_DOCUMENT}.
*/
public static final int TOKENIZER_TYPE_NONE = 0;
/** Tokenization for plain text. */
public static final int TOKENIZER_TYPE_PLAIN = 1;
private final com.google.android.icing.proto.IndexingConfig mProto;
private IndexingConfig(com.google.android.icing.proto.IndexingConfig proto) {
mProto = proto;
}
/**
* Builder for {@link IndexingConfig} objects.
*
* <p>You may skip adding an {@link IndexingConfig} for a property, which is equivalent to
* an {@link IndexingConfig} having {@code termMatchType} equal to
* {@link #TERM_MATCH_TYPE_UNKNOWN}. In this case the property will not be indexed.
*/
public static final class Builder {
private final com.google.android.icing.proto.IndexingConfig.Builder mProtoBuilder =
com.google.android.icing.proto.IndexingConfig.newBuilder();
private Builder() {}
/** Configures how the content of this property should be matched in the index. */
@NonNull
public IndexingConfig.Builder setTermMatchType(@TermMatchType int termMatchType) {
com.google.android.icing.proto.TermMatchType.Code termMatchTypeProto =
com.google.android.icing.proto.TermMatchType.Code.forNumber(termMatchType);
if (termMatchTypeProto == null) {
throw new IllegalArgumentException("Invalid termMatchType: " + termMatchType);
}
mProtoBuilder.setTermMatchType(termMatchTypeProto);
return this;
}
/** Configures how this property should be tokenized (split into words). */
@NonNull
public IndexingConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
com.google.android.icing.proto.IndexingConfig.TokenizerType.Code
tokenizerTypeProto =
com.google.android.icing.proto.IndexingConfig
.TokenizerType.Code.forNumber(tokenizerType);
if (tokenizerTypeProto == null) {
throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType);
}
mProtoBuilder.setTokenizerType(tokenizerTypeProto);
return this;
}
/**
* Constructs a new {@link IndexingConfig} from the contents of this builder.
*
* <p>After calling this method, the builder must no longer be used.
*/
@NonNull
public IndexingConfig build() {
return new IndexingConfig(mProtoBuilder.build());
}
}
}
}

View File

@ -14,6 +14,18 @@
* limitations under the License.
*/
package android.app.appsearch;
import com.android.internal.infra.AndroidFuture;
/** {@hide} */
interface IAppSearchManager {
/**
* Sets the schema.
*
* @param schemaProto serialized SchemaProto
* @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, in AndroidFuture callback);
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2019 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.NonNull;
/**
* Indicates that a {@link android.app.appsearch.AppSearchSchema} has logical inconsistencies such
* as unpopulated mandatory fields or illegal combinations of parameters.
*
* @hide
*/
public class IllegalSchemaException extends IllegalArgumentException {
/**
* Constructs a new {@link IllegalSchemaException}.
*
* @param message A developer-readable description of the issue with the bundle.
*/
public IllegalSchemaException(@NonNull String message) {
super(message);
}
}

View File

@ -12,17 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
java_library {
name: "service-appsearch",
installable: true,
srcs: [
"java/**/*.java",
],
libs: [
"framework",
"services.core",
],
static_libs: [
"icing-java-proto-lite",
],
apex_available: [ "com.android.appsearch" ],
name: "service-appsearch",
installable: true,
srcs: ["java/**/*.java"],
libs: [
"framework",
"framework-appsearch",
"services.core",
],
static_libs: ["icing-java-proto-lite"],
apex_available: ["com.android.appsearch"],
}

View File

@ -18,8 +18,11 @@ package com.android.server.appsearch;
import android.app.appsearch.IAppSearchManager;
import android.content.Context;
import com.android.internal.infra.AndroidFuture;
import com.android.server.SystemService;
import com.google.android.icing.proto.SchemaProto;
/**
* TODO(b/142567528): add comments when implement this class
*/
@ -35,5 +38,14 @@ public class AppSearchManagerService extends SystemService {
}
private class Stub extends IAppSearchManager.Stub {
@Override
public void setSchema(byte[] schemaBytes, AndroidFuture callback) {
try {
SchemaProto schema = SchemaProto.parseFrom(schemaBytes);
throw new UnsupportedOperationException("setSchema not yet implemented: " + schema);
} catch (Throwable t) {
callback.completeExceptionally(t);
}
}
}
}

View File

@ -36,8 +36,6 @@ import java.util.concurrent.atomic.AtomicInteger;
* <p>
* Currently, only queries by single exact term are supported. There is no support for persistence,
* namespaces, i18n tokenization, or schema.
*
* @hide
*/
public class FakeIcing {
private final AtomicInteger mNextDocId = new AtomicInteger();