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

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

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

parents e0afc542 fb081c93
Loading
Loading
Loading
Loading
+1 −8
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import com.android.internal.infra.AndroidFuture;

import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.StatusProto;
import com.google.protobuf.InvalidProtocolBufferException;

@@ -294,13 +293,7 @@ public class AppSearchManager {
        //     them in one big list.
        AndroidFuture<AppSearchResult> searchResultFuture = new AndroidFuture<>();
        try {
            SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto();
            searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build();
            mService.query(
                    searchSpecProto.toByteArray(),
                    searchSpec.getResultSpecProto().toByteArray(),
                    searchSpec.getScoringSpecProto().toByteArray(),
                    searchResultFuture);
            mService.query(queryExpression, searchSpec.getBundle(), searchResultFuture);
        } catch (RemoteException e) {
            searchResultFuture.completeExceptionally(e);
        }
+4 −4
Original line number Diff line number Diff line
@@ -67,14 +67,14 @@ interface IAppSearchManager {
    /**
     * Searches a document based on a given specifications.
     *
     * @param searchSpecBytes Serialized SearchSpecProto.
     * @param resultSpecBytes Serialized SearchResultsProto.
     * @param scoringSpecBytes Serialized ScoringSpecProto.
     * @param queryExpression String to search for
     * @param searchSpecBundle SearchSpec bundle
     * @param callback {@link AndroidFuture}&lt;{@link AppSearchResult}&lt;{@link byte[]}&gt;&gt;
     *     Will be completed with a serialized {@link SearchResultsProto}.
     */
    void query(
        in byte[] searchSpecBytes, in byte[] resultSpecBytes, in byte[] scoringSpecBytes,
        in String queryExpression,
        in Bundle searchSpecBundle,
        in AndroidFuture<AppSearchResult> callback);

    /**
+145 −93
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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,19 +16,17 @@

package android.app.appsearch;

import android.os.Bundle;

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

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


/**
 * This class represents the specification logic for AppSearch. It can be used to set the type of
 * search, like prefix or exact only or apply filters to search for a specific schema type only etc.
@@ -36,67 +34,100 @@ import java.lang.annotation.RetentionPolicy;
 */
// TODO(sidchhabra) : AddResultSpec fields for Snippets etc.
public final class SearchSpec {
    /** @hide */
    
    private final SearchSpecProto mSearchSpecProto;
    private final ResultSpecProto mResultSpecProto;
    private final ScoringSpecProto mScoringSpecProto;
    public static final String TERM_MATCH_TYPE_FIELD = "termMatchType";

    private SearchSpec(@NonNull SearchSpecProto searchSpecProto,
            @NonNull ResultSpecProto resultSpecProto, @NonNull ScoringSpecProto scoringSpecProto) {
        mSearchSpecProto = searchSpecProto;
        mResultSpecProto = resultSpecProto;
        mScoringSpecProto = scoringSpecProto;
    }
    /** @hide */
    
    /** Creates a new {@link SearchSpec.Builder}. */
    @NonNull
    public static SearchSpec.Builder newBuilder() {
        return new SearchSpec.Builder();
    }
    public static final String SCHEMA_TYPES_FIELD = "schemaType";

    /** @hide */
    @NonNull
    SearchSpecProto getSearchSpecProto() {
        return mSearchSpecProto;
    }
    
    public static final String NAMESPACE_FIELD = "namespace";

    /** @hide */
    @NonNull
    ResultSpecProto getResultSpecProto() {
        return mResultSpecProto;
    }
    
    public static final String NUM_PER_PAGE_FIELD = "numPerPage";

    /** @hide */
    
    public static final String RANKING_STRATEGY_FIELD = "rankingStrategy";

    /** @hide */
    
    public static final String ORDER_FIELD = "order";

    /** @hide */
    
    public static final String SNIPPET_COUNT_FIELD = "snippetCount";

    /** @hide */
    
    public static final String SNIPPET_COUNT_PER_PROPERTY_FIELD = "snippetCountPerProperty";

    /** @hide */
    
    public static final String MAX_SNIPPET_FIELD = "maxSnippet";

    /** @hide */
    
    public static final int DEFAULT_NUM_PER_PAGE = 10;

    private static final int MAX_NUM_PER_PAGE = 10_000;
    private static final int MAX_SNIPPET_COUNT = 10_000;
    private static final int MAX_SNIPPET_PER_PROPERTY_COUNT = 10_000;
    private static final int MAX_SNIPPET_SIZE_LIMIT = 10_000;

    private final Bundle mBundle;

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

    /**
     * Returns the {@link Bundle} populated by this builder.
     * @hide
     */
    @NonNull
    ScoringSpecProto getScoringSpecProto() {
        return mScoringSpecProto;
    public Bundle getBundle() {
        return mBundle;
    }

    /** Term Match Type for the query. */
    /**
     * Term Match Type for the query.
     * @hide
     */
    // NOTE: The integer values of these constants must match the proto enum constants in
    // {@link com.google.android.icing.proto.SearchSpecProto.termMatchType}
    @IntDef(prefix = {"TERM_MATCH_TYPE_"}, value = {
            TERM_MATCH_TYPE_EXACT_ONLY,
            TERM_MATCH_TYPE_PREFIX
    @IntDef(value = {
            TERM_MATCH_EXACT_ONLY,
            TERM_MATCH_PREFIX
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface TermMatchTypeCode {}
    public @interface TermMatchCode {}

    /**
     * Query terms will only match exact tokens in the index.
     * <p>Ex. A query term "foo" will only match indexed token "foo", and not "foot" or "football".
     */
    public static final int TERM_MATCH_TYPE_EXACT_ONLY = 1;
    public static final int TERM_MATCH_EXACT_ONLY = 1;
    /**
     * Query terms will match indexed tokens when the query term is a prefix of the token.
     * <p>Ex. A query term "foo" will match indexed tokens like "foo", "foot", and "football".
     */
    public static final int TERM_MATCH_TYPE_PREFIX = 2;
    public static final int TERM_MATCH_PREFIX = 2;

    /** Ranking Strategy for query result.*/
    /**
     * Ranking Strategy for query result.
     * @hide
     */
    // NOTE: The integer values of these constants must match the proto enum constants in
    // {@link ScoringSpecProto.RankingStrategy.Code }
    @IntDef(prefix = {"RANKING_STRATEGY_"}, value = {
    @IntDef(value = {
            RANKING_STRATEGY_NONE,
            RANKING_STRATEGY_DOCUMENT_SCORE,
            RANKING_STRATEGY_CREATION_TIMESTAMP
@@ -111,10 +142,13 @@ public final class SearchSpec {
    /** Ranked by document creation timestamps. */
    public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2;

    /** Order for query result.*/
    /**
     * Order for query result.
     * @hide
     */
    // NOTE: The integer values of these constants must match the proto enum constants in
    // {@link ScoringSpecProto.Order.Code }
    @IntDef(prefix = {"ORDER_"}, value = {
    @IntDef(value = {
            ORDER_DESCENDING,
            ORDER_ASCENDING
    })
@@ -129,27 +163,24 @@ public final class SearchSpec {
    /** Builder for {@link SearchSpec objects}. */
    public static final class Builder {

        private final SearchSpecProto.Builder mSearchSpecBuilder = SearchSpecProto.newBuilder();
        private final ResultSpecProto.Builder mResultSpecBuilder = ResultSpecProto.newBuilder();
        private final ScoringSpecProto.Builder mScoringSpecBuilder = ScoringSpecProto.newBuilder();
        private final ResultSpecProto.SnippetSpecProto.Builder mSnippetSpecBuilder =
                ResultSpecProto.SnippetSpecProto.newBuilder();
        private final Bundle mBundle;
        private boolean mBuilt = false;

        private Builder() {
        /** Creates a new {@link SearchSpec.Builder}. */
        public Builder() {
            mBundle = new Bundle();
            mBundle.putInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
        }

        /**
         * Indicates how the query terms should match {@link TermMatchTypeCode} in the index.
         * Indicates how the query terms should match {@code TermMatchCode} in the index.
         */
        @NonNull
        public Builder setTermMatchType(@TermMatchTypeCode int termMatchTypeCode) {
            TermMatchType.Code termMatchTypeCodeProto =
                    TermMatchType.Code.forNumber(termMatchTypeCode);
            if (termMatchTypeCodeProto == null) {
                throw new IllegalArgumentException("Invalid term match type: "
                        + termMatchTypeCode);
            }
            mSearchSpecBuilder.setTermMatchType(termMatchTypeCodeProto);
        public Builder setTermMatch(@TermMatchCode int termMatchTypeCode) {
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            Preconditions.checkArgumentInRange(termMatchTypeCode, TERM_MATCH_EXACT_ONLY,
                    TERM_MATCH_PREFIX, "Term match type");
            mBundle.putInt(TERM_MATCH_TYPE_FIELD, termMatchTypeCode);
            return this;
        }

@@ -160,31 +191,44 @@ public final class SearchSpec {
         */
        @NonNull
        public Builder setSchemaTypes(@NonNull String... schemaTypes) {
            for (String schemaType : schemaTypes) {
                mSearchSpecBuilder.addSchemaTypeFilters(schemaType);
            Preconditions.checkNotNull(schemaTypes);
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            mBundle.putStringArray(SCHEMA_TYPES_FIELD, schemaTypes);
            return this;
        }

        /**
         * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that
         * have the specified namespaces.
         * <p>If unset, the query will search over all namespaces.
         */
        @NonNull
        public Builder setNamespaces(@NonNull String... namespaces) {
            Preconditions.checkNotNull(namespaces);
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            mBundle.putStringArray(NAMESPACE_FIELD, namespaces);
            return this;
        }

        /** Sets the maximum number of results to retrieve from the query */
        /**
         * Sets the number of results per page in the returned object.
         * <p> The default number of results per page is 10. And should be set in range [0, 10k].
         */
        @NonNull
        public SearchSpec.Builder setNumToRetrieve(int numToRetrieve) {
            // Just retrieve everything in one page.
            // TODO(b/152359656): Realign these two apis properly.
            mResultSpecBuilder.setNumPerPage(numToRetrieve);
        public SearchSpec.Builder setNumPerPage(int numPerPage) {
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            Preconditions.checkArgumentInRange(numPerPage, 0, MAX_NUM_PER_PAGE, "NumPerPage");
            mBundle.putInt(NUM_PER_PAGE_FIELD, numPerPage);
            return this;
        }

        /** Sets ranking strategy for AppSearch results.*/
        @NonNull
        public Builder setRankingStrategy(@RankingStrategyCode int rankingStrategy) {
            ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto =
                    ScoringSpecProto.RankingStrategy.Code.forNumber(rankingStrategy);
            if (rankingStrategyCodeProto == null) {
                throw new IllegalArgumentException("Invalid result ranking strategy: "
                        + rankingStrategyCodeProto);
            }
            mScoringSpecBuilder.setRankBy(rankingStrategyCodeProto);
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            Preconditions.checkArgumentInRange(rankingStrategy, RANKING_STRATEGY_NONE,
                    RANKING_STRATEGY_CREATION_TIMESTAMP, "Result ranking strategy");
            mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy);
            return this;
        }

@@ -195,37 +239,41 @@ public final class SearchSpec {
         */
        @NonNull
        public Builder setOrder(@OrderCode int order) {
            ScoringSpecProto.Order.Code orderCodeProto =
                    ScoringSpecProto.Order.Code.forNumber(order);
            if (orderCodeProto == null) {
                throw new IllegalArgumentException("Invalid result ranking order: "
                        + orderCodeProto);
            }
            mScoringSpecBuilder.setOrderBy(orderCodeProto);
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            Preconditions.checkArgumentInRange(order, ORDER_DESCENDING, ORDER_ASCENDING,
                    "Result ranking order");
            mBundle.putInt(ORDER_FIELD, order);
            return this;
        }

        /**
         * Only the first {@code numToSnippet} documents based on the ranking strategy
         * Only the first {@code snippetCount} documents based on the ranking strategy
         * will have snippet information provided.
         * <p>If set to 0 (default), snippeting is disabled and
         * {@link SearchResults.Result#getMatchInfo} will return {@code null} for that result.
         * {@link SearchResults.Result#getMatches} will return {@code null} for that result.
         * <p>The value should be set in range[0, 10k].
         */
        @NonNull
        public SearchSpec.Builder setNumToSnippet(int numToSnippet) {
            mSnippetSpecBuilder.setNumToSnippet(numToSnippet);
        public SearchSpec.Builder setSnippetCount(int snippetCount) {
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            Preconditions.checkArgumentInRange(snippetCount, 0, MAX_SNIPPET_COUNT, "snippetCount");
            mBundle.putInt(SNIPPET_COUNT_FIELD, snippetCount);
            return this;
        }

        /**
         * Only the first {@code numMatchesPerProperty} matches for a every property of
         * {@link AppSearchDocument} will contain snippet information.
         * <p>If set to 0, snippeting is disabled and {@link SearchResults.Result#getMatchInfo}
         * Only the first {@code matchesCountPerProperty} matches for a every property of
         * {@link GenericDocument} will contain snippet information.
         * <p>If set to 0, snippeting is disabled and {@link SearchResults.Result#getMatches}
         * will return {@code null} for that result.
         * <p>The value should be set in range[0, 10k].
         */
        @NonNull
        public SearchSpec.Builder setNumMatchesPerProperty(int numMatchesPerProperty) {
            mSnippetSpecBuilder.setNumMatchesPerProperty(numMatchesPerProperty);
        public SearchSpec.Builder setSnippetCountPerProperty(int snippetCountPerProperty) {
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            Preconditions.checkArgumentInRange(snippetCountPerProperty,
                    0, MAX_SNIPPET_PER_PROPERTY_COUNT, "snippetCountPerProperty");
            mBundle.putInt(SNIPPET_COUNT_PER_PROPERTY_FIELD, snippetCountPerProperty);
            return this;
        }

@@ -238,10 +286,14 @@ public final class SearchSpec {
         * be returned. If matches enabled is also set to false, then snippeting is disabled.
         * <p>Ex. {@code maxSnippetSize} = 16. "foo bar baz bat rat" with a query of "baz" will
         * return a window of "bar baz bat" which is only 11 bytes long.
         * <p>The value should be in range[0, 10k].
         */
        @NonNull
        public SearchSpec.Builder setMaxSnippetSize(int maxSnippetSize) {
            mSnippetSpecBuilder.setMaxWindowBytes(maxSnippetSize);
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            Preconditions.checkArgumentInRange(
                    maxSnippetSize, 0, MAX_SNIPPET_SIZE_LIMIT, "maxSnippetSize");
            mBundle.putInt(MAX_SNIPPET_FIELD, maxSnippetSize);
            return this;
        }

@@ -252,12 +304,12 @@ public final class SearchSpec {
         */
        @NonNull
        public SearchSpec build() {
            if (mSearchSpecBuilder.getTermMatchType() == TermMatchType.Code.UNKNOWN) {
            Preconditions.checkState(!mBuilt, "Builder has already been used");
            if (!mBundle.containsKey(TERM_MATCH_TYPE_FIELD)) {
                throw new IllegalSearchSpecException("Missing termMatchType field.");
            }
            mResultSpecBuilder.setSnippetSpec(mSnippetSpecBuilder);
            return new SearchSpec(mSearchSpecBuilder.build(), mResultSpecBuilder.build(),
                    mScoringSpecBuilder.build());
            mBuilt = true;
            return new SearchSpec(mBundle);
        }
    }
}
+18 −24
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.app.appsearch.AppSearchDocument;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.IAppSearchManager;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.os.Binder;
@@ -32,15 +33,13 @@ import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
import com.android.server.appsearch.external.localbackend.AppSearchImpl;
import com.android.server.appsearch.external.localbackend.converter.SchemaToProtoConverter;
import com.android.server.appsearch.external.localbackend.converter.SearchSpecToProtoConverter;

import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.ResultSpecProto;
import com.google.android.icing.proto.SchemaProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;
import com.google.android.icing.proto.ScoringSpecProto;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.StatusProto;

import java.io.IOException;
import java.util.List;
@@ -160,36 +159,31 @@ public class AppSearchManagerService extends SystemService {
        // TODO(sidchhabra): Do this in a threadpool.
        @Override
        public void query(
                @NonNull byte[] searchSpecBytes,
                @NonNull byte[] resultSpecBytes,
                @NonNull byte[] scoringSpecBytes,
                @NonNull String queryExpression,
                @NonNull Bundle searchSpecBundle,
                @NonNull AndroidFuture<AppSearchResult> callback) {
            Preconditions.checkNotNull(searchSpecBytes);
            Preconditions.checkNotNull(resultSpecBytes);
            Preconditions.checkNotNull(scoringSpecBytes);
            Preconditions.checkNotNull(queryExpression);
            Preconditions.checkNotNull(searchSpecBundle);
            Preconditions.checkNotNull(callback);
            int callingUid = Binder.getCallingUidOrThrow();
            int callingUserId = UserHandle.getUserId(callingUid);
            final long callingIdentity = Binder.clearCallingIdentity();
            try {
                SearchSpecProto searchSpecProto = SearchSpecProto.parseFrom(searchSpecBytes);
                ResultSpecProto resultSpecProto = ResultSpecProto.parseFrom(resultSpecBytes);
                ScoringSpecProto scoringSpecProto = ScoringSpecProto.parseFrom(scoringSpecBytes);
                SearchSpec searchSpec = new SearchSpec(searchSpecBundle);
                SearchSpecProto searchSpecProto =
                        SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
                searchSpecProto = searchSpecProto.toBuilder()
                        .setQuery(queryExpression).build();
                AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
                String databaseName = makeDatabaseName(callingUid);
                // TODO(adorokhine): handle pagination
                SearchResultProto searchResultProto = impl.query(
                        databaseName, searchSpecProto, resultSpecProto, scoringSpecProto);
                // TODO(sidchhabra): Translate SearchResultProto errors into error codes. This might
                //     better be done in AppSearchImpl by throwing an AppSearchException.
                if (searchResultProto.getStatus().getCode() != StatusProto.Code.OK) {
                    callback.complete(
                            AppSearchResult.newFailedResult(
                                    AppSearchResult.RESULT_INTERNAL_ERROR,
                                    searchResultProto.getStatus().getMessage()));
                } else {
                        databaseName,
                        searchSpecProto,
                        SearchSpecToProtoConverter.toResultSpecProto(searchSpec),
                        SearchSpecToProtoConverter.toScoringSpecProto(searchSpec));
                callback.complete(
                        AppSearchResult.newSuccessfulResult(searchResultProto.toByteArray()));
                }
            } catch (Throwable t) {
                callback.complete(throwableToFailedResult(t));
            } finally {
+111 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading