Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0a26313a authored by Alexander Dorokhine's avatar Alexander Dorokhine
Browse files

Remove IndexingConfig and merge its constants into PropertyConfig.

With 4-tab indents, these nested structures become difficult to write
and maintain. The information encoded here doesn't seem to benefit from
being separated into its own config.

Test: atest CtsAppSearchTestCases FrameworksCoreTests:android.app.appsearch FrameworksServicesTests:com.android.server.appsearch.impl
Bug: 145635424
Change-Id: I6c771e0af1ca34a05b09d1532f0f227a069e0d2e
parent 07bb8239
Loading
Loading
Loading
Loading
+13 −5
Original line number Diff line number Diff line
@@ -71,10 +71,10 @@ public class AppSearchManager {
     *     <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}.
     * @param schemas The schema configs for the types used by the calling app.
     *
     * @hide
     */
@@ -84,11 +84,19 @@ public class AppSearchManager {
    // 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();
            @NonNull Consumer<? super Throwable> callback,
            @NonNull AppSearchSchema... schemas) {
        // Prepare the merged schema for transmission.
        SchemaProto.Builder schemaProtoBuilder = SchemaProto.newBuilder();
        for (AppSearchSchema schema : schemas) {
            schemaProtoBuilder.addTypes(schema.getProto());
        }

        // Serialize and send the schema.
        // TODO: This should use com.android.internal.infra.RemoteStream or another mechanism to
        //  avoid binder limits.
        byte[] schemaBytes = schemaProtoBuilder.build().toByteArray();
        AndroidFuture<Void> future = new AndroidFuture<>();
        try {
            mService.setSchema(schemaBytes, future);
+142 −199
Original line number Diff line number Diff line
@@ -18,41 +18,38 @@ package android.app.appsearch;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.util.ArraySet;

import com.android.internal.annotations.VisibleForTesting;

import com.google.android.icing.proto.PropertyConfigProto;
import com.google.android.icing.proto.SchemaProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;
import com.google.android.icing.proto.TermMatchType;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Set;

/**
 * Representation of the AppSearch Schema.
 * The AppSearch Schema for a particular type of document.
 *
 * <p>The schema is the set of document types, properties, and config (like tokenization type)
 * understood by AppSearch for this app.
 * <p>For example, an e-mail message or a music recording could be a schema type.
 *
 * <p>The schema consists of type information, properties, and config (like tokenization type).
 *
 * @hide
 */
public final class AppSearchSchema {
    private final SchemaProto mProto;
    private final SchemaTypeConfigProto mProto;

    private AppSearchSchema(SchemaProto proto) {
    private AppSearchSchema(SchemaTypeConfigProto 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);
    public static AppSearchSchema.Builder newBuilder(@NonNull String typeName) {
        return new AppSearchSchema.Builder(typeName);
    }

    /** Creates a new {@link PropertyConfig.Builder}. */
@@ -61,59 +58,22 @@ public final class AppSearchSchema {
        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.
     * Returns the {@link SchemaTypeConfigProto} populated by this builder.
     * @hide
     */
    @NonNull
    @VisibleForTesting
    public SchemaProto getProto() {
    public SchemaTypeConfigProto 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;
    @Override
    public String toString() {
        return mProto.toString();
    }

        /**
         * 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. */
    /** Builder for {@link AppSearchSchema objects}. */
    public static final class Builder {
        private final SchemaTypeConfigProto.Builder mProtoBuilder =
                SchemaTypeConfigProto.newBuilder();
@@ -124,21 +84,28 @@ public final class AppSearchSchema {

        /** Adds a property to the given type. */
        @NonNull
            public SchemaType.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
        public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
            mProtoBuilder.addProperties(propertyConfig.mProto);
            return this;
        }

        /**
             * Constructs a new {@link SchemaType} from the contents of this builder.
         * 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 SchemaType build() {
                return new SchemaType(mProtoBuilder.build());
        public AppSearchSchema build() {
            Set<String> propertyNames = new ArraySet<>();
            for (PropertyConfigProto propertyConfigProto : mProtoBuilder.getPropertiesList()) {
                if (!propertyNames.add(propertyConfigProto.getPropertyName())) {
                    throw new IllegalSchemaException(
                            "Property defined more than once: "
                                    + propertyConfigProto.getPropertyName());
                }
            }
            return new AppSearchSchema(mProtoBuilder.build());
        }
    }

    /**
@@ -197,12 +164,71 @@ public final class AppSearchSchema {
        /** Exactly one value [1]. */
        public static final int CARDINALITY_REQUIRED = 3;

        /** Encapsulates the configurations on how AppSearch should query/index these terms. */
        @IntDef(prefix = {"INDEXING_TYPE_"}, value = {
                INDEXING_TYPE_NONE,
                INDEXING_TYPE_EXACT_TERMS,
                INDEXING_TYPE_PREFIXES,
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface IndexingType {}

        /**
         * 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 indexingType} for the nested properties.
         */
        public static final int INDEXING_TYPE_NONE = 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 INDEXING_TYPE_EXACT_TERMS = 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 INDEXING_TYPE_PREFIXES = 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 PropertyConfigProto mProto;

        private PropertyConfig(PropertyConfigProto proto) {
            mProto = proto;
        }

        @Override
        public String toString() {
            return mProto.toString();
        }

        /**
         * Builder for {@link PropertyConfig}.
         *
@@ -217,11 +243,14 @@ public final class AppSearchSchema {
         * is also required.
         */
        public static final class Builder {
            private final PropertyConfigProto.Builder mProtoBuilder =
            private final PropertyConfigProto.Builder mPropertyConfigProto =
                    PropertyConfigProto.newBuilder();
            private final com.google.android.icing.proto.IndexingConfig.Builder
                    mIndexingConfigProto =
                        com.google.android.icing.proto.IndexingConfig.newBuilder();

            private Builder(String propertyName) {
                mProtoBuilder.setPropertyName(propertyName);
                mPropertyConfigProto.setPropertyName(propertyName);
            }

            /**
@@ -236,7 +265,7 @@ public final class AppSearchSchema {
                if (dataTypeProto == null) {
                    throw new IllegalArgumentException("Invalid dataType: " + dataType);
                }
                mProtoBuilder.setDataType(dataTypeProto);
                mPropertyConfigProto.setDataType(dataTypeProto);
                return this;
            }

@@ -248,7 +277,7 @@ public final class AppSearchSchema {
             */
            @NonNull
            public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
                mProtoBuilder.setSchemaType(schemaType);
                mPropertyConfigProto.setSchemaType(schemaType);
                return this;
            }

@@ -264,19 +293,44 @@ public final class AppSearchSchema {
                if (cardinalityProto == null) {
                    throw new IllegalArgumentException("Invalid cardinality: " + cardinality);
                }
                mProtoBuilder.setCardinality(cardinalityProto);
                mPropertyConfigProto.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.
             * Configures how a property should be indexed so that it can be retrieved by queries.
             */
            @NonNull
            public PropertyConfig.Builder setIndexingConfig(
                    @NonNull IndexingConfig indexingConfig) {
                mProtoBuilder.setIndexingConfig(indexingConfig.mProto);
            public PropertyConfig.Builder setIndexingType(@IndexingType int indexingType) {
                TermMatchType.Code termMatchTypeProto;
                switch (indexingType) {
                    case INDEXING_TYPE_NONE:
                        termMatchTypeProto = TermMatchType.Code.UNKNOWN;
                        break;
                    case INDEXING_TYPE_EXACT_TERMS:
                        termMatchTypeProto = TermMatchType.Code.EXACT_ONLY;
                        break;
                    case INDEXING_TYPE_PREFIXES:
                        termMatchTypeProto = TermMatchType.Code.PREFIX;
                        break;
                    default:
                        throw new IllegalArgumentException("Invalid indexingType: " + indexingType);
                }
                mIndexingConfigProto.setTermMatchType(termMatchTypeProto);
                return this;
            }

            /** Configures how this property should be tokenized (split into words). */
            @NonNull
            public PropertyConfig.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);
                }
                mIndexingConfigProto.setTokenizerType(tokenizerTypeProto);
                return this;
            }

@@ -290,136 +344,25 @@ public final class AppSearchSchema {
             */
            @NonNull
            public PropertyConfig build() {
                if (mProtoBuilder.getDataType() == PropertyConfigProto.DataType.Code.UNKNOWN) {
                mPropertyConfigProto.setIndexingConfig(mIndexingConfigProto);
                // TODO(b/147692920): Send the schema to Icing Lib for official validation, instead
                //     of partially reimplementing some of the validation Icing does here.
                if (mPropertyConfigProto.getDataType()
                        == PropertyConfigProto.DataType.Code.UNKNOWN) {
                    throw new IllegalSchemaException("Missing field: dataType");
                }
                if (mProtoBuilder.getSchemaType().isEmpty()
                        && mProtoBuilder.getDataType()
                if (mPropertyConfigProto.getSchemaType().isEmpty()
                        && mPropertyConfigProto.getDataType()
                            == PropertyConfigProto.DataType.Code.DOCUMENT) {
                    throw new IllegalSchemaException(
                            "Missing field: schemaType (required for configs with "
                                    + "dataType = DOCUMENT)");
                }
                if (mProtoBuilder.getCardinality()
                if (mPropertyConfigProto.getCardinality()
                        == PropertyConfigProto.Cardinality.Code.UNKNOWN) {
                    throw new IllegalSchemaException("Missing field: cardinality");
                }
                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());
                return new PropertyConfig(mPropertyConfigProto.build());
            }
        }
    }
+95 −83

File changed.

Preview size limit exceeded, changes collapsed.