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

Commit 5cebdd69 authored by Alexander Dorokhine's avatar Alexander Dorokhine Committed by Terry Wang
Browse files

Add SearchResults and support pagination in query.

Bug: 162450968
Test: presubmit
Change-Id: I23f892cae5c8c953adc5665def0d80fc56614350
parent 66940ba5
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