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

Commit 4b01b414 authored by Siddhartha Chhabra's avatar Siddhartha Chhabra Committed by Android (Google) Code Review
Browse files

Merge "Added query and SearchResults."

parents fb342fcf 16e5fefb
Loading
Loading
Loading
Loading
+94 −1
Original line number Original line Diff line number Diff line
/*
/*
 * Copyright (C) 2019 The Android Open Source Project
 * Copyright (C) 2020 The Android Open Source Project
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * you may not use this file except in compliance with the License.
@@ -25,9 +25,12 @@ import android.os.RemoteException;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.AndroidFuture;


import com.google.android.icing.proto.SchemaProto;
import com.google.android.icing.proto.SchemaProto;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.protobuf.InvalidProtocolBufferException;


import java.util.List;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Consumer;


/**
/**
@@ -127,4 +130,94 @@ public class AppSearchManager {
        // TODO(b/147614371) Fix error report for multiple documents.
        // TODO(b/147614371) Fix error report for multiple documents.
        future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
        future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
    }
    }

    /**
     * This method searches for documents based on a given query string. It also accepts
     * specifications regarding how to search and format the results.
     *
     *<p>Currently we support following features in the raw query format:
     * <ul>
     *     <li>AND
     *     AND joins (e.g. “match documents that have both the terms ‘dog’ and
     *     ‘cat’”).
     *     Example: hello world matches documents that have both ‘hello’ and ‘world’
     *     <li>OR
     *     OR joins (e.g. “match documents that have either the term ‘dog’ or
     *     ‘cat’”).
     *     Example: dog OR puppy
     *     <li>Exclusion
     *     Exclude a term (e.g. “match documents that do
     *     not have the term ‘dog’”).
     *     Example: -dog excludes the term ‘dog’
     *     <li>Grouping terms
     *     Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
     *     “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
     *     Example: (dog puppy) (cat kitten) two one group containing two terms.
     *     <li>Property restricts
     *      which properties of a document to specifically match terms in (e.g.
     *     “match documents where the ‘subject’ property contains ‘important’”).
     *     Example: subject:important matches documents with the term ‘important’ in the
     *     ‘subject’ property
     *     <li>Schema type restricts
     *     This is similar to property restricts, but allows for restricts on top-level document
     *     fields, such as schema_type. Clients should be able to limit their query to documents of
     *     a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”).
     *     Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents
     *     that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
     *     ‘Video’ schema type.
     * </ul>
     *
     * <p> It is strongly recommended to use Jetpack APIs.
     *
     * @param queryExpression Query String to search.
     * @param searchSpec Spec for setting filters, raw query etc.
     * @param executor Executor on which to invoke the callback.
     * @param callback  Callback to receive errors resulting from the query operation. If the
     *                 operation succeeds, the callback will be invoked with {@code null}.
     * @hide
     */
    @NonNull
    public void query(
            @NonNull String queryExpression,
            @NonNull SearchSpec searchSpec,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull BiConsumer<? super SearchResults, ? super Throwable> callback) {
        AndroidFuture<byte[]> future = new AndroidFuture<>();
        future.whenCompleteAsync((searchResultBytes, err) -> {
            if (err != null) {
                callback.accept(null, err);
                return;
            }

            if (searchResultBytes != null) {
                SearchResultProto searchResultProto;
                try {
                    searchResultProto = SearchResultProto.parseFrom(searchResultBytes);
                } catch (InvalidProtocolBufferException e) {
                    callback.accept(null, e);
                    return;
                }
                if (searchResultProto.hasError()) {
                    // TODO(sidchhabra): Add better exception handling.
                    callback.accept(
                            null,
                            new RuntimeException(searchResultProto.getError().getErrorMessage()));
                    return;
                }
                SearchResults searchResults = new SearchResults(searchResultProto);
                callback.accept(searchResults, null);
                return;
            }

            // Nothing was supplied in the future at all
            callback.accept(
                    null, new IllegalStateException("Unknown failure occurred while querying"));
        }, executor);

        try {
            mService.query(queryExpression, searchSpec.getProto().toByteArray(), future);
        } catch (RemoteException e) {
            future.completeExceptionally(e);
        }
    }
}
}
+10 −1
Original line number Original line Diff line number Diff line
/**
/**
 * Copyright 2019, The Android Open Source Project
 * Copyright 2020, The Android Open Source Project
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * you may not use this file except in compliance with the License.
@@ -29,4 +29,13 @@ interface IAppSearchManager {
     */
     */
    void setSchema(in byte[] schemaProto, in AndroidFuture callback);
    void setSchema(in byte[] schemaProto, in AndroidFuture callback);
    void put(in byte[] documentBytes, in AndroidFuture callback);
    void put(in byte[] documentBytes, in AndroidFuture callback);
    /**
     * Searches a document based on a given query string.
     *
     * @param queryExpression Query String to search.
     * @param searchSpec Serialized SearchSpecProto.
     * @param callback {@link AndroidFuture}. Will be completed with a serialized
     *     {@link SearchResultsProto}, or completed exceptionally if query fails.
     */
     void query(in String queryExpression, in byte[] searchSpecBytes, in AndroidFuture callback);
}
}
+36 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 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.
 * 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.SearchResults} has logical inconsistencies such
 * as unpopulated mandatory fields or illegal combinations of parameters.
 *
 * @hide
 */
public class IllegalSearchSpecException extends IllegalArgumentException {
    /**
     * Constructs a new {@link IllegalSearchSpecException}.
     *
     * @param message A developer-readable description of the issue with the bundle.
     */
    public IllegalSearchSpecException(@NonNull String message) {
        super(message);
    }
}
+89 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 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.
 * 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;

import com.google.android.icing.proto.SearchResultProto;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * SearchResults are a list of results that are returned from a query. Each result from this
 * list contains a document and may contain other fields like snippets based on request.
 * @hide
 */
public final class SearchResults {

    private final SearchResultProto mSearchResultProto;

    /** @hide */
    public SearchResults(SearchResultProto searchResultProto) {
        mSearchResultProto = searchResultProto;
    }

    /**
     * This class represents the result obtained from the query. It will contain the document which
     * which matched the specified query string and specifications.
     * @hide
     */
    public static final class Result {
        private final SearchResultProto.ResultProto mResultProto;

        private Result(SearchResultProto.ResultProto resultProto) {
            mResultProto = resultProto;
        }

        /**
         * Contains the matching {@link AppSearch.Document}.
         * @return Document object which matched the query.
         * @hide
         */
        // TODO(sidchhabra): Switch to Document constructor that takes proto.
        @NonNull
        public AppSearch.Document getDocument() {
            return AppSearch.Document.newBuilder(mResultProto.getDocument().getUri(),
                    mResultProto.getDocument().getSchema())
                    .setCreationTimestampSecs(mResultProto.getDocument().getCreationTimestampSecs())
                    .setScore(mResultProto.getDocument().getScore())
                    .build();
        }

        // TODO(sidchhabra): Add Getter for ResultReader for Snippet.
    }

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

    /**
     * Returns a {@link Result} iterator. Returns Empty Iterator if there are no matching results.
     * @hide
     */
    @NonNull
    public Iterator<Result> getResults() {
        List<Result> results = new ArrayList<>();
        // TODO(sidchhabra): Pass results using a RemoteStream.
        for (SearchResultProto.ResultProto resultProto : mSearchResultProto.getResultsList()) {
            results.add(new Result(resultProto));
        }
        return results.iterator();
    }
}
+127 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 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.
 * 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.SearchSpecProto;
import com.google.android.icing.proto.TermMatchType;

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.
 * @hide
 *
 */
// TODO(sidchhabra) : AddResultSpec fields for Snippets etc.
public final class SearchSpec {

    private final SearchSpecProto mSearchSpecProto;

    private SearchSpec(SearchSpecProto searchSpecProto) {
        mSearchSpecProto = searchSpecProto;
    }

    /** Creates a new {@link SearchSpec.Builder}. */
    @NonNull
    public static SearchSpec.Builder newBuilder() {
        return new SearchSpec.Builder();
    }

    /** @hide */
    @NonNull
    SearchSpecProto getProto() {
        return mSearchSpecProto;
    }

    /** Term Match Type for the query. */
    // 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
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface TermMatchTypeCode {}

    public static final int TERM_MATCH_TYPE_EXACT_ONLY = 1;
    public static final int TERM_MATCH_TYPE_PREFIX = 2;

    /** Builder for {@link SearchSpec objects}. */
    public static final class Builder {

        private final SearchSpecProto.Builder mBuilder = SearchSpecProto.newBuilder();

        private Builder(){}

        /**
         * Indicates how the query terms should match {@link TermMatchTypeCode} in the index.
         *
         *   TermMatchType.Code=EXACT_ONLY
         *   Query terms will only match exact tokens in the index.
         *   Ex. A query term "foo" will only match indexed token "foo", and not "foot"
         *   or "football"
         *
         *   TermMatchType.Code=PREFIX
         *   Query terms will match indexed tokens when the query term is a prefix of
         *   the token.
         *   Ex. A query term "foo" will match indexed tokens like "foo", "foot", and
         *   "football".
         */
        @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);
            }
            mBuilder.setTermMatchType(termMatchTypeCodeProto);
            return this;
        }

        /**
         * Adds a Schema type filter to {@link SearchSpec} Entry.
         * Only search for documents that have the specified schema types.
         * If unset, the query will search over all schema types.
         */
        @NonNull
        public Builder setSchemaTypes(@NonNull String... schemaTypes) {
            for (String schemaType : schemaTypes) {
                mBuilder.addSchemaTypeFilters(schemaType);
            }
            return this;
        }

        /**
         * Constructs a new {@link SearchSpec} from the contents of this builder.
         *
         * <p>After calling this method, the builder must no longer be used.
         */
        @NonNull
        public SearchSpec build() {
            if (mBuilder.getTermMatchType() == TermMatchType.Code.UNKNOWN) {
                throw new IllegalSearchSpecException("Missing termMatchType field.");
            }
            return new SearchSpec(mBuilder.build());
        }
    }

}
Loading