From 516b009a7fb3597fe965cb21b98b1206e71c2bb9 Mon Sep 17 00:00:00 2001 From: Terry Wang Date: Fri, 6 Nov 2020 15:46:44 -0800 Subject: [PATCH] Add GlobalSearchSession to AppSearch platform. Bug: 162450968 Test: presubmit Change-Id: Icbe3a4b61c2cf0776b5a887f800836a007f236b6 --- .../app/appsearch/AppSearchManager.java | 19 +++ .../app/appsearch/GlobalSearchSession.java | 128 ++++++++++++++++++ .../app/appsearch/IAppSearchManager.aidl | 14 ++ .../android/app/appsearch/SearchResults.java | 32 +++-- .../appsearch/AppSearchManagerService.java | 29 ++++ 5 files changed, 208 insertions(+), 14 deletions(-) create mode 100644 apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index 1af387dadf4bc..5fd45eadbda97 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -140,6 +140,25 @@ public class AppSearchManager { AppSearchSession.createSearchSession(searchContext, mService, executor, callback); } + /** + * Creates a new {@link GlobalSearchSession}. + * + *

This process requires an AppSearch native indexing file system for each user. If it's not + * created for this user, the initialization process will create one under user's directory. + * + * @param executor Executor on which to invoke the callback. + * @param callback The {@link AppSearchResult}<{@link GlobalSearchSession}> of + * performing this operation. Or a {@link AppSearchResult} with failure + * reason code and error information. + */ + public void createGlobalSearchSession( + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + GlobalSearchSession.createGlobalSearchSession(mService, executor, callback); + } + /** * Sets the schema being used by documents provided to the {@link #putDocuments} method. * diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java new file mode 100644 index 0000000000000..d2aa8eab4708d --- /dev/null +++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java @@ -0,0 +1,128 @@ +/* + * 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.CallbackExecutor; +import android.annotation.NonNull; +import android.os.RemoteException; + +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * This class provides global access to the centralized AppSearch index maintained by the system. + * + *

Apps can retrieve indexed documents through the query API. + * @hide + */ +public class GlobalSearchSession { + + private final IAppSearchManager mService; + + static void createGlobalSearchSession( + @NonNull IAppSearchManager service, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer> callback) { + GlobalSearchSession globalSearchSession = new GlobalSearchSession(service); + globalSearchSession.initialize(executor, callback); + } + + // NOTE: No instance of this class should be created or returned except via initialize(). + // Once the callback.accept has been called here, the class is ready to use. + private void initialize( + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer> callback) { + try { + mService.initialize(new IAppSearchResultCallback.Stub() { + public void onResult(AppSearchResult result) { + executor.execute(() -> { + if (result.isSuccess()) { + callback.accept( + AppSearchResult.newSuccessfulResult(GlobalSearchSession.this)); + } else { + callback.accept(result); + } + }); + } + }); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private GlobalSearchSession(@NonNull IAppSearchManager service) { + mService = service; + } + + /** + * Searches across all documents in the storage based on a given query string. + * + *

Currently we support following features in the raw query format: + *

+ * + *

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 globalQuery( + @NonNull String queryExpression, + @NonNull SearchSpec searchSpec, + @NonNull @CallbackExecutor Executor executor) { + Objects.requireNonNull(queryExpression); + Objects.requireNonNull(searchSpec); + Objects.requireNonNull(executor); + return new SearchResults(mService, /*databaseName=*/null, queryExpression, + searchSpec, executor); + } +} diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index e09abe540fe06..22e00f2cdfdc3 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -94,6 +94,20 @@ interface IAppSearchManager { in Bundle searchSpecBundle, in IAppSearchResultCallback callback); + /** + * Executes a global query, i.e. over all permitted databases, against the AppSearch index and + * returns results. + * + * @param queryExpression String to search for + * @param searchSpecBundle SearchSpec bundle + * @param callback {@link AppSearchResult}<{@link Bundle}> of performing this + * operation. + */ + void globalQuery( + in String queryExpression, + in Bundle searchSpecBundle, + 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. diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java index aec69bb492ddf..8548d209c7871 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java +++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java @@ -85,23 +85,18 @@ public class SearchResults implements Closeable { 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)); - } - }); + if (mDatabaseName == null) { + mService.globalQuery(mQueryExpression, mSearchSpec.getBundle(), + wrapCallback(callback)); + } else { + mService.query(mDatabaseName, mQueryExpression, mSearchSpec.getBundle(), + wrapCallback(callback)); + } } else { - mService.getNextPage(mNextPageToken, - new IAppSearchResultCallback.Stub() { - public void onResult(AppSearchResult result) { - mExecutor.execute(() -> invokeCallback(result, callback)); - } - }); + mService.getNextPage(mNextPageToken, wrapCallback(callback)); } } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -131,4 +126,13 @@ public class SearchResults implements Closeable { } }); } + + private IAppSearchResultCallback wrapCallback( + @NonNull Consumer>> callback) { + return new IAppSearchResultCallback.Stub() { + public void onResult(AppSearchResult result) { + mExecutor.execute(() -> invokeCallback(result, callback)); + } + }; + } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java index f706b407c5d16..d5146dd75c3b7 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -68,6 +68,7 @@ public class AppSearchManagerService extends SystemService { @NonNull IAppSearchResultCallback callback) { Preconditions.checkNotNull(databaseName); Preconditions.checkNotNull(schemaBundles); + Preconditions.checkNotNull(callback); int callingUid = Binder.getCallingUidOrThrow(); int callingUserId = UserHandle.getUserId(callingUid); final long callingIdentity = Binder.clearCallingIdentity(); @@ -166,6 +167,7 @@ public class AppSearchManagerService extends SystemService { Preconditions.checkNotNull(databaseName); Preconditions.checkNotNull(queryExpression); Preconditions.checkNotNull(searchSpecBundle); + Preconditions.checkNotNull(callback); int callingUid = Binder.getCallingUidOrThrow(); int callingUserId = UserHandle.getUserId(callingUid); final long callingIdentity = Binder.clearCallingIdentity(); @@ -185,9 +187,34 @@ public class AppSearchManagerService extends SystemService { } } + public void globalQuery( + @NonNull String queryExpression, + @NonNull Bundle searchSpecBundle, + @NonNull IAppSearchResultCallback callback) { + Preconditions.checkNotNull(queryExpression); + Preconditions.checkNotNull(searchSpecBundle); + Preconditions.checkNotNull(callback); + int callingUid = Binder.getCallingUidOrThrow(); + int callingUserId = UserHandle.getUserId(callingUid); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId); + SearchResultPage searchResultPage = impl.globalQuery( + queryExpression, + new SearchSpec(searchSpecBundle)); + invokeCallbackOnResult(callback, + AppSearchResult.newSuccessfulResult(searchResultPage.getBundle())); + } catch (Throwable t) { + invokeCallbackOnError(callback, t); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + @Override public void getNextPage(long nextPageToken, @NonNull IAppSearchResultCallback callback) { + Preconditions.checkNotNull(callback); int callingUid = Binder.getCallingUidOrThrow(); int callingUserId = UserHandle.getUserId(callingUid); final long callingIdentity = Binder.clearCallingIdentity(); @@ -261,6 +288,7 @@ public class AppSearchManagerService extends SystemService { Preconditions.checkNotNull(databaseName); Preconditions.checkNotNull(queryExpression); Preconditions.checkNotNull(searchSpecBundle); + Preconditions.checkNotNull(callback); int callingUid = Binder.getCallingUidOrThrow(); int callingUserId = UserHandle.getUserId(callingUid); final long callingIdentity = Binder.clearCallingIdentity(); @@ -279,6 +307,7 @@ public class AppSearchManagerService extends SystemService { @Override public void initialize(@NonNull IAppSearchResultCallback callback) { + Preconditions.checkNotNull(callback); int callingUid = Binder.getCallingUidOrThrow(); int callingUserId = UserHandle.getUserId(callingUid); final long callingIdentity = Binder.clearCallingIdentity();