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

Commit bf7f6352 authored by Terry Wang's avatar Terry Wang Committed by Android (Google) Code Review
Browse files

Merge "Add SearchResults and support pagination in query."

parents 0d824d33 5cebdd69
Loading
Loading
Loading
Loading
+17 −13
Original line number Diff line number Diff line
@@ -383,22 +383,26 @@ public class AppSearchManager {
            @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
        // TODO(b/146386470): Transmit the result documents as a RemoteStream instead of sending
        //     them in one big list.
        AndroidFuture<AppSearchResult> searchResultsFuture = new AndroidFuture<>();
        AndroidFuture<AppSearchResult> future = new AndroidFuture<>();
        try {
            mService.query(DEFAULT_DATABASE_NAME, queryExpression,
                    searchSpec.getBundle(), searchResultsFuture);
        } catch (RemoteException e) {
            searchResultsFuture.completeExceptionally(e);
            mService.query(DEFAULT_DATABASE_NAME, queryExpression, searchSpec.getBundle(),
                    new IAppSearchResultCallback.Stub() {
                        public void onResult(AppSearchResult result) {
                            future.complete(result);
                        }

        // Translate the Bundle into a searchResultPage.
        AppSearchResult<Bundle> bundleResult = getFutureOrThrow(searchResultsFuture);
                    });
            AppSearchResult<Bundle> bundleResult = getFutureOrThrow(future);
            if (!bundleResult.isSuccess()) {
                return AppSearchResult.newFailedResult(bundleResult.getResultCode(),
                        bundleResult.getErrorMessage());
            }
            SearchResultPage searchResultPage = new SearchResultPage(bundleResult.getResultValue());
            return AppSearchResult.newSuccessfulResult(searchResultPage.getResults());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } catch (Throwable t) {
            return AppSearchResult.throwableToFailedResult(t);
        }
    }

    /**
+1 −1
Original line number Diff line number Diff line
@@ -16,4 +16,4 @@
package android.app.appsearch;

/** {@hide} */
parcelable AppSearchResult;
 No newline at end of file
parcelable AppSearchResult<ValueType>;
 No newline at end of file
+55 −2
Original line number Diff line number Diff line
@@ -269,6 +269,61 @@ public final class AppSearchSession {
        }
    }

    /**
     * Searches a document based on a given query string.
     *
     * <p>Currently we support following features in the raw query format:
     * <ul>
     *     <li>AND
     *     <p>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
     *     <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
     *     ‘cat’”).
     *     Example: dog OR puppy
     *     <li>Exclusion
     *     <p>Exclude a term (e.g. “match documents that do
     *     not have the term ‘dog’”).
     *     Example: -dog excludes the term ‘dog’
     *     <li>Grouping terms
     *     <p>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
     *     <p> Specifies 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
     *     <p>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> This method is lightweight. The heavy work will be done in
     * {@link SearchResults#getNextPage}.
     *
     * @param queryExpression Query String to search.
     * @param searchSpec      Spec for setting filters, raw query etc.
     * @param executor        Executor on which to invoke the callback of the following request
     *                        {@link SearchResults#getNextPage}.
     * @return The search result of performing this operation.
     */
    @NonNull
    public SearchResults query(
            @NonNull String queryExpression,
            @NonNull SearchSpec searchSpec,
            @NonNull @CallbackExecutor Executor executor) {
        Objects.requireNonNull(queryExpression);
        Objects.requireNonNull(searchSpec);
        Objects.requireNonNull(executor);
        return new SearchResults(mService, mDatabaseName, queryExpression, searchSpec, executor);
    }

    /**
     * Removes {@link GenericDocument}s from the index by URI.
     *
@@ -342,6 +397,4 @@ public final class AppSearchSession {
            throw e.rethrowFromSystemServer();
        }
    }

    // TODO(b/162450968) port query() and SearchResults.java to platform.
}
+21 −2
Original line number Diff line number Diff line
@@ -85,13 +85,32 @@ interface IAppSearchManager {
     * @param databaseName The databaseName this query for.
     * @param queryExpression String to search for
     * @param searchSpecBundle SearchSpec bundle
     * @param callback {@link AndroidFuture}&lt;{@link AppSearchResult}&lt;{@link SearchResults}&gt;&gt;
     * @param callback {@link AppSearchResult}&lt;{@link Bundle}&gt; of performing this
     *         operation.
     */
    void query(
        in String databaseName,
        in String queryExpression,
        in Bundle searchSpecBundle,
        in AndroidFuture<AppSearchResult> callback);
        in IAppSearchResultCallback callback);

    /**
     * Fetches the next page of results of a previously executed query. Results can be empty if
     * next-page token is invalid or all pages have been returned.
     *
     * @param nextPageToken The token of pre-loaded results of previously executed query.
     * @param callback {@link AppSearchResult}&lt;{@link Bundle}&gt; of performing this
     *                  operation.
     */
    void getNextPage(in long nextPageToken, in IAppSearchResultCallback callback);

    /**
     * Invalidates the next-page token so that no more results of the related query can be returned.
     *
     * @param nextPageToken The token of pre-loaded results of previously executed query to be
     *                      Invalidated.
     */
    void invalidateNextPageToken(in long nextPageToken);

    /**
     * Removes documents by URI.
+98 −42
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,63 +16,119 @@

package android.app.appsearch;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;

import java.util.ArrayList;
import com.android.internal.util.Preconditions;

import java.io.Closeable;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * Structure for transmitting a page of search results across binder.
 * SearchResults are a returned object from a query API.
 *
 * <p>Each {@link SearchResult} contains a document and may contain other fields like snippets
 * based on request.
 *
 * <p>Should close this object after finish fetching results.
 *
 * <p>This class is not thread safe.
 * @hide
 */
public final class SearchResults implements Parcelable {
    final List<SearchResult> mResults;
    final long mNextPageToken;
public class SearchResults implements Closeable {
    private static final String TAG = "SearchResults";

    private final IAppSearchManager mService;

    @Nullable
    private final String mDatabaseName;

    private final String mQueryExpression;

    public SearchResults(@NonNull List<SearchResult> results, long nextPageToken) {
        mResults = results;
        mNextPageToken = nextPageToken;
    private final SearchSpec mSearchSpec;

    private final Executor mExecutor;

    private long mNextPageToken;

    private boolean mIsFirstLoad = true;

    SearchResults(@NonNull IAppSearchManager service,
            @Nullable String databaseName,
            @NonNull String queryExpression,
            @NonNull SearchSpec searchSpec,
            @NonNull @CallbackExecutor Executor executor) {
        mService = Preconditions.checkNotNull(service);
        mExecutor = Preconditions.checkNotNull(executor);
        mDatabaseName = databaseName;
        mQueryExpression = Preconditions.checkNotNull(queryExpression);
        mSearchSpec = Preconditions.checkNotNull(searchSpec);
    }

    private SearchResults(@NonNull Parcel in) {
        List<Bundle> resultBundles = in.readArrayList(/*loader=*/ null);
        mResults = new ArrayList<>(resultBundles.size());
        for (int i = 0; i < resultBundles.size(); i++) {
            SearchResult searchResult = new SearchResult(resultBundles.get(i));
            mResults.add(searchResult);
    /**
     * Gets a whole page of {@link SearchResult}s.
     *
     * <p>Re-call this method to get next page of {@link SearchResult}, until it returns an
     * empty list.
     *
     * <p>The page size is set by {@link SearchSpec.Builder#setNumPerPage}.
     *
     * @param callback Callback to receive the pending result of performing this operation.
     */
    public void getNextPage(@NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
        try {
            if (mIsFirstLoad) {
                mIsFirstLoad = false;
                //TODO(b/162450968) add support for global query.
                mService.query(mDatabaseName, mQueryExpression, mSearchSpec.getBundle(),
                        new IAppSearchResultCallback.Stub() {
                            public void onResult(AppSearchResult result) {
                                mExecutor.execute(() -> invokeCallback(result, callback));
                            }
        mNextPageToken = in.readLong();
                        });
            } else {
                mService.getNextPage(mNextPageToken,
                        new IAppSearchResultCallback.Stub() {
                            public void onResult(AppSearchResult result) {
                                mExecutor.execute(() -> invokeCallback(result, callback));
                            }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        List<Bundle> resultBundles = new ArrayList<>(mResults.size());
        for (int i = 0; i < mResults.size(); i++) {
            resultBundles.add(mResults.get(i).getBundle());
                        });
            }
        dest.writeList(resultBundles);
        dest.writeLong(mNextPageToken);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<SearchResults> CREATOR = new Creator<SearchResults>() {
        @NonNull
        @Override
        public SearchResults createFromParcel(@NonNull Parcel in) {
            return new SearchResults(in);
    private void invokeCallback(AppSearchResult result,
            @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) {
        if (result.isSuccess()) {
            try {
                SearchResultPage searchResultPage =
                        new SearchResultPage((Bundle) result.getResultValue());
                mNextPageToken = searchResultPage.getNextPageToken();
                callback.accept(AppSearchResult.newSuccessfulResult(
                        searchResultPage.getResults()));
            } catch (Throwable t) {
                callback.accept(AppSearchResult.throwableToFailedResult(t));
            }
        } else {
            callback.accept(result);
        }
    }

        @NonNull
    @Override
        public SearchResults[] newArray(int size) {
            return new SearchResults[size];
    public void close() {
        mExecutor.execute(() -> {
            try {
                mService.invalidateNextPageToken(mNextPageToken);
            } catch (RemoteException e) {
                Log.d(TAG, "Unable to close the SearchResults", e);
            }
        });
    }
    };
}
Loading