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

Commit a91a221e authored by Alexander Dorokhine's avatar Alexander Dorokhine Committed by Android (Google) Code Review
Browse files

Merge "Merge Jetpack AppSearchSchema work from last two quarters."

parents 54416f51 6cfcc574
Loading
Loading
Loading
Loading
+6 −10
Original line number Diff line number Diff line
@@ -18,12 +18,12 @@ package android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;

import com.android.internal.infra.AndroidFuture;

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;
import com.google.android.icing.proto.StatusProto;
@@ -133,19 +133,15 @@ public class AppSearchManager {
    @NonNull
    public AppSearchResult<Void> setSchema(
            @NonNull List<AppSearchSchema> schemas, boolean forceOverride) {
        // 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();
        List<Bundle> schemaBundles = new ArrayList<>(schemas.size());
        for (AppSearchSchema schema : schemas) {
            schemaBundles.add(schema.getBundle());
        }
        AndroidFuture<AppSearchResult> future = new AndroidFuture<>();
        try {
            mService.setSchema(schemaBytes, forceOverride, future);
            mService.setSchema(schemaBundles, forceOverride, future);
        } catch (RemoteException e) {
            future.completeExceptionally(e);
        }
+127 −93
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 * Copyright 2020 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.
@@ -16,18 +16,18 @@

package android.app.appsearch;

import android.os.Bundle;

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.SchemaTypeConfigProto;
import com.google.android.icing.proto.TermMatchType;
import android.app.appsearch.exceptions.IllegalSchemaException;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;

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

/**
@@ -36,45 +36,64 @@ import java.util.Set;
 * <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 SchemaTypeConfigProto mProto;
    /** @hide */
    
    public static final String SCHEMA_TYPE_FIELD = "schemaType";

    private AppSearchSchema(SchemaTypeConfigProto proto) {
        mProto = proto;
    /** @hide */
    
    public static final String PROPERTIES_FIELD = "properties";

    private final Bundle mBundle;

    /** @hide */
    
    public AppSearchSchema(@NonNull Bundle bundle) {
        Preconditions.checkNotNull(bundle);
        mBundle = bundle;
    }

    /**
     * Returns the {@link SchemaTypeConfigProto} populated by this builder.
     * Returns the {@link Bundle} populated by this builder.
     * @hide
     */
    
    @NonNull
    @VisibleForTesting
    public SchemaTypeConfigProto getProto() {
        return mProto;
    public Bundle getBundle() {
        return mBundle;
    }

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

    /** Builder for {@link AppSearchSchema objects}. */
    public static final class Builder {
        private final SchemaTypeConfigProto.Builder mProtoBuilder =
                SchemaTypeConfigProto.newBuilder();
        private final String mTypeName;
        private final ArrayList<Bundle> mProperties = new ArrayList<>();
        private final Set<String> mPropertyNames = new ArraySet<>();
        private boolean mBuilt = false;

        /** Creates a new {@link AppSearchSchema.Builder}. */
        public Builder(@NonNull String typeName) {
            mProtoBuilder.setSchemaType(typeName);
            Preconditions.checkNotNull(typeName);
            mTypeName = typeName;
        }

        /** Adds a property to the given type. */
        @NonNull
        public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
            mProtoBuilder.addProperties(propertyConfig.mProto);
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            Preconditions.checkNotNull(propertyConfig);
            if (!mPropertyNames.add(propertyConfig.mName)) {
                throw new IllegalSchemaException(
                        "Property defined more than once: " + propertyConfig.mName);
            }
            mProperties.add(propertyConfig.mBundle);
            return this;
        }

@@ -85,15 +104,12 @@ public final class AppSearchSchema {
         */
        @NonNull
        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());
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            Bundle bundle = new Bundle();
            bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mTypeName);
            bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mProperties);
            mBuilt = true;
            return new AppSearchSchema(bundle);
        }
    }

@@ -104,10 +120,37 @@ public final class AppSearchSchema {
     * a property.
     */
    public static final class PropertyConfig {
        /** Physical data-types of the contents of the property. */
        /** @hide */
        
        public static final String NAME_FIELD = "name";

        /** @hide */
        
        public static final String DATA_TYPE_FIELD = "dataType";

        /** @hide */
        
        public static final String SCHEMA_TYPE_FIELD = "schemaType";

        /** @hide */
        
        public static final String CARDINALITY_FIELD = "cardinality";

        /** @hide */
        
        public static final String INDEXING_TYPE_FIELD = "indexingType";

        /** @hide */
        
        public static final String TOKENIZER_TYPE_FIELD = "tokenizerType";

        /**
         * Physical data-types of the contents of the property.
         * @hide
         */
        // 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 = {
        @IntDef(value = {
                DATA_TYPE_STRING,
                DATA_TYPE_INT64,
                DATA_TYPE_DOUBLE,
@@ -133,10 +176,13 @@ public final class AppSearchSchema {
         */
        public static final int DATA_TYPE_DOCUMENT = 6;

        /** The cardinality of the property (whether it is required, optional or repeated). */
        /**
         * The cardinality of the property (whether it is required, optional or repeated).
         * @hide
         */
        // 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 = {
        @IntDef(value = {
                CARDINALITY_REPEATED,
                CARDINALITY_OPTIONAL,
                CARDINALITY_REQUIRED,
@@ -153,8 +199,11 @@ 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 = {
        /**
         * Encapsulates the configurations on how AppSearch should query/index these terms.
         * @hide
         */
        @IntDef(value = {
                INDEXING_TYPE_NONE,
                INDEXING_TYPE_EXACT_TERMS,
                INDEXING_TYPE_PREFIXES,
@@ -188,10 +237,13 @@ public final class AppSearchSchema {
         */
        public static final int INDEXING_TYPE_PREFIXES = 2;

        /** Configures how tokens should be extracted from this property. */
        /**
         * Configures how tokens should be extracted from this property.
         * @hide
         */
        // 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 = {
        @IntDef(value = {
                TOKENIZER_TYPE_NONE,
                TOKENIZER_TYPE_PLAIN,
        })
@@ -207,15 +259,17 @@ public final class AppSearchSchema {
        /** Tokenization for plain text. */
        public static final int TOKENIZER_TYPE_PLAIN = 1;

        private final PropertyConfigProto mProto;
        final String mName;
        final Bundle mBundle;

        private PropertyConfig(PropertyConfigProto proto) {
            mProto = proto;
        PropertyConfig(@NonNull String name, @NonNull Bundle bundle) {
            mName = Preconditions.checkNotNull(name);
            mBundle = Preconditions.checkNotNull(bundle);
        }

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

        /**
@@ -232,15 +286,14 @@ public final class AppSearchSchema {
         * is also required.
         */
        public static final class Builder {
            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 final String mName;
            private final Bundle mBundle = new Bundle();
            private boolean mBuilt = false;

            /** Creates a new {@link PropertyConfig.Builder}. */
            public Builder(@NonNull String propertyName) {
                mPropertyConfigProto.setPropertyName(propertyName);
                mName = Preconditions.checkNotNull(propertyName);
                mBundle.putString(NAME_FIELD, propertyName);
            }

            /**
@@ -250,24 +303,24 @@ public final class AppSearchSchema {
             */
            @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);
                }
                mPropertyConfigProto.setDataType(dataTypeProto);
                Preconditions.checkState(!mBuilt, "Builder has already been used");
                Preconditions.checkArgumentInRange(
                        dataType, DATA_TYPE_STRING, DATA_TYPE_DOCUMENT, "dataType");
                mBundle.putInt(DATA_TYPE_FIELD, dataType);
                return this;
            }

            /**
             * The logical schema-type of the contents of this property.
             *
             * <p>Only required when {@link #setDataType(int)} is set to
             * <p>Only required when {@link #setDataType} is set to
             * {@link #DATA_TYPE_DOCUMENT}. Otherwise, it is ignored.
             */
            @NonNull
            public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
                mPropertyConfigProto.setSchemaType(schemaType);
                Preconditions.checkState(!mBuilt, "Builder has already been used");
                Preconditions.checkNotNull(schemaType);
                mBundle.putString(SCHEMA_TYPE_FIELD, schemaType);
                return this;
            }

@@ -278,12 +331,10 @@ public final class AppSearchSchema {
             */
            @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);
                }
                mPropertyConfigProto.setCardinality(cardinalityProto);
                Preconditions.checkState(!mBuilt, "Builder has already been used");
                Preconditions.checkArgumentInRange(
                        cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
                mBundle.putInt(CARDINALITY_FIELD, cardinality);
                return this;
            }

@@ -292,35 +343,20 @@ public final class AppSearchSchema {
             */
            @NonNull
            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);
                Preconditions.checkState(!mBuilt, "Builder has already been used");
                Preconditions.checkArgumentInRange(
                        indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_PREFIXES, "indexingType");
                mBundle.putInt(INDEXING_TYPE_FIELD, indexingType);
                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);
                Preconditions.checkState(!mBuilt, "Builder has already been used");
                Preconditions.checkArgumentInRange(
                        tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_PLAIN, "tokenizerType");
                mBundle.putInt(TOKENIZER_TYPE_FIELD, tokenizerType);
                return this;
            }

@@ -334,25 +370,23 @@ public final class AppSearchSchema {
             */
            @NonNull
            public PropertyConfig build() {
                mPropertyConfigProto.setIndexingConfig(mIndexingConfigProto);
                Preconditions.checkState(!mBuilt, "Builder has already been used");
                // 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) {
                if (!mBundle.containsKey(DATA_TYPE_FIELD)) {
                    throw new IllegalSchemaException("Missing field: dataType");
                }
                if (mPropertyConfigProto.getSchemaType().isEmpty()
                        && mPropertyConfigProto.getDataType()
                            == PropertyConfigProto.DataType.Code.DOCUMENT) {
                if (mBundle.getString(SCHEMA_TYPE_FIELD, "").isEmpty()
                        && mBundle.getInt(DATA_TYPE_FIELD) == DATA_TYPE_DOCUMENT) {
                    throw new IllegalSchemaException(
                            "Missing field: schemaType (required for configs with "
                                    + "dataType = DOCUMENT)");
                }
                if (mPropertyConfigProto.getCardinality()
                        == PropertyConfigProto.Cardinality.Code.UNKNOWN) {
                if (!mBundle.containsKey(CARDINALITY_FIELD)) {
                    throw new IllegalSchemaException("Missing field: cardinality");
                }
                return new PropertyConfig(mPropertyConfigProto.build());
                mBuilt = true;
                return new PropertyConfig(mName, mBundle);
            }
        }
    }
+6 −2
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
 */
package android.app.appsearch;

import android.os.Bundle;

import com.android.internal.infra.AndroidFuture;

parcelable AppSearchResult;
@@ -25,14 +27,16 @@ interface IAppSearchManager {
    /**
     * Sets the schema.
     *
     * @param schemaBytes Serialized SchemaProto.
     * @param schemaBundles List of AppSearchSchema bundles.
     * @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 AppSearchResult}&lt;{@link Void}&gt&gt;.
     *     The results of the call.
     */
    void setSchema(
        in byte[] schemaBytes, boolean forceOverride, in AndroidFuture<AppSearchResult> callback);
        in List<Bundle> schemaBundles,
        boolean forceOverride,
        in AndroidFuture<AppSearchResult> callback);

    /**
     * Inserts documents into the index.
+1 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.app.appsearch;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.appsearch.exceptions.IllegalSearchSpecException;

import com.google.android.icing.proto.ResultSpecProto;
import com.google.android.icing.proto.ScoringSpecProto;
+4 −2
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 * Copyright 2020 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.
@@ -14,16 +14,18 @@
 * limitations under the License.
 */

package android.app.appsearch;
package android.app.appsearch.exceptions;

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}.
Loading