Loading apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +17 −13 Original line number Diff line number Diff line Loading @@ -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); } } /** Loading apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -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 apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +55 −2 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -342,6 +397,4 @@ public final class AppSearchSession { throw e.rethrowFromSystemServer(); } } // TODO(b/162450968) port query() and SearchResults.java to platform. } apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +21 −2 Original line number Diff line number Diff line Loading @@ -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}<{@link AppSearchResult}<{@link SearchResults}>> * @param callback {@link AppSearchResult}<{@link Bundle}> 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}<{@link Bundle}> 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. Loading apex/appsearch/framework/java/android/app/appsearch/SearchResults.java +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. Loading @@ -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
apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +17 −13 Original line number Diff line number Diff line Loading @@ -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); } } /** Loading
apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -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
apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +55 −2 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -342,6 +397,4 @@ public final class AppSearchSession { throw e.rethrowFromSystemServer(); } } // TODO(b/162450968) port query() and SearchResults.java to platform. }
apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +21 −2 Original line number Diff line number Diff line Loading @@ -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}<{@link AppSearchResult}<{@link SearchResults}>> * @param callback {@link AppSearchResult}<{@link Bundle}> 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}<{@link Bundle}> 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. Loading
apex/appsearch/framework/java/android/app/appsearch/SearchResults.java +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. Loading @@ -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); } }); } }; }