diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.aidl b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.aidl
new file mode 100644
index 0000000000000..4686de8df268e
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ * 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;
+
+/** {@hide} */
+parcelable AppSearchBatchResult;
\ No newline at end of file
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index f2c9942edbb32..e57359fbf50ed 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -15,10 +15,12 @@
*/
package android.app.appsearch;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
import android.os.Bundle;
+import android.os.ParcelableException;
import android.os.RemoteException;
import com.android.internal.infra.AndroidFuture;
@@ -27,7 +29,10 @@ import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* This class provides access to the centralized AppSearch index maintained by the system.
@@ -40,12 +45,99 @@ import java.util.concurrent.ExecutionException;
// TODO(b/148046169): This class header needs a detailed example/tutorial.
@SystemService(Context.APP_SEARCH_SERVICE)
public class AppSearchManager {
- private static final String DEFAULT_DATABASE = "";
+ /**
+ * The default empty database name.
+ * @hide
+ */
+ public static final String DEFAULT_DATABASE_NAME = "";
+
private final IAppSearchManager mService;
/** @hide */
public AppSearchManager(@NonNull IAppSearchManager service) {
- mService = service;
+ mService = Objects.requireNonNull(service);
+ }
+
+ /** Contains information about how to create the search session. */
+ public static final class SearchContext {
+ final String mDatabaseName;
+
+ SearchContext(@NonNull String databaseName) {
+ mDatabaseName = Objects.requireNonNull(databaseName);
+ }
+
+ /**
+ * Returns the name of the database to create or open.
+ *
+ *
Databases with different names are fully separate with distinct types, namespaces,
+ * and data.
+ */
+ @NonNull
+ public String getDatabaseName() {
+ return mDatabaseName;
+ }
+
+ /** Builder for {@link SearchContext} objects. */
+ public static final class Builder {
+ private String mDatabaseName = DEFAULT_DATABASE_NAME;
+ private boolean mBuilt = false;
+
+ /**
+ * Sets the name of the database associated with {@link AppSearchSession}.
+ *
+ *
{@link AppSearchSession} will create or open a database under the given name.
+ *
+ *
Databases with different names are fully separate with distinct types, namespaces,
+ * and data.
+ *
+ *
Database name cannot contain {@code '/'}.
+ *
+ *
If not specified, defaults to {@link #DEFAULT_DATABASE_NAME}.
+ * @param databaseName The name of the database.
+ * @throws IllegalArgumentException if the databaseName contains {@code '/'}.
+ */
+ @NonNull
+ public Builder setDatabaseName(@NonNull String databaseName) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Objects.requireNonNull(databaseName);
+ if (databaseName.contains("/")) {
+ throw new IllegalArgumentException("Database name cannot contain '/'");
+ }
+ mDatabaseName = databaseName;
+ return this;
+ }
+
+ /** Builds a {@link SearchContext} instance. */
+ @NonNull
+ public SearchContext build() {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBuilt = true;
+ return new SearchContext(mDatabaseName);
+ }
+ }
+ }
+
+ /**
+ * Creates a new {@link AppSearchSession}.
+ *
+ *
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 searchContext The {@link SearchContext} contains all information to create a new
+ * {@link AppSearchSession}
+ * @param executor Executor on which to invoke the callback.
+ * @param callback The {@link AppSearchResult}<{@link AppSearchSession}> of
+ * performing this operation. Or a {@link AppSearchResult} with failure
+ * reason code and error information.
+ */
+ public void createSearchSession(
+ @NonNull SearchContext searchContext,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer> callback) {
+ Objects.requireNonNull(searchContext);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ AppSearchSession.createSearchSession(searchContext, mService, executor, callback);
}
/**
@@ -99,7 +191,7 @@ public class AppSearchManager {
*
* @param request The schema update request.
* @return the result of performing this operation.
- *
+ * @deprecated use {@link AppSearchSession#setSchema} instead.
* @hide
*/
@NonNull
@@ -113,9 +205,14 @@ public class AppSearchManager {
}
AndroidFuture future = new AndroidFuture<>();
try {
- mService.setSchema(DEFAULT_DATABASE, schemaBundles, request.isForceOverride(), future);
+ mService.setSchema(DEFAULT_DATABASE_NAME, schemaBundles, request.isForceOverride(),
+ new IAppSearchResultCallback.Stub() {
+ public void onResult(AppSearchResult result) {
+ future.complete(result);
+ }
+ });
} catch (RemoteException e) {
- future.completeExceptionally(e);
+ throw e.rethrowFromSystemServer();
}
return getFutureOrThrow(future);
}
@@ -134,6 +231,9 @@ public class AppSearchManager {
* {@link AppSearchBatchResult} are the URIs of the input documents. The values are
* {@code null} if they were successfully indexed, or a failed {@link AppSearchResult}
* otherwise.
+ * @throws RuntimeException If an error occurred during the execution.
+ *
+ * @deprecated use {@link AppSearchSession#putDocuments} instead.
* @hide
*/
public AppSearchBatchResult putDocuments(@NonNull PutDocumentsRequest request) {
@@ -146,7 +246,16 @@ public class AppSearchManager {
}
AndroidFuture future = new AndroidFuture<>();
try {
- mService.putDocuments(DEFAULT_DATABASE, documentBundles, future);
+ mService.putDocuments(DEFAULT_DATABASE_NAME, documentBundles,
+ new IAppSearchBatchResultCallback.Stub() {
+ public void onResult(AppSearchBatchResult result) {
+ future.complete(result);
+ }
+
+ public void onSystemError(ParcelableException exception) {
+ future.completeExceptionally(exception);
+ }
+ });
} catch (RemoteException e) {
future.completeExceptionally(e);
}
@@ -165,6 +274,9 @@ public class AppSearchManager {
* {@link GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise.
* URIs that are not found will return a failed {@link AppSearchResult} with a result code
* of {@link AppSearchResult#RESULT_NOT_FOUND}.
+ * @throws RuntimeException If an error occurred during the execution.
+ *
+ * @deprecated use {@link AppSearchSession#getByUri} instead.
*/
public AppSearchBatchResult getByUri(
@NonNull GetByUriRequest request) {
@@ -173,7 +285,16 @@ public class AppSearchManager {
List uris = new ArrayList<>(request.getUris());
AndroidFuture future = new AndroidFuture<>();
try {
- mService.getDocuments(DEFAULT_DATABASE, request.getNamespace(), uris, future);
+ mService.getDocuments(DEFAULT_DATABASE_NAME, request.getNamespace(), uris,
+ new IAppSearchBatchResultCallback.Stub() {
+ public void onResult(AppSearchBatchResult result) {
+ future.complete(result);
+ }
+
+ public void onSystemError(ParcelableException exception) {
+ future.completeExceptionally(exception);
+ }
+ });
} catch (RemoteException e) {
future.completeExceptionally(e);
}
@@ -252,6 +373,9 @@ public class AppSearchManager {
*
* @param queryExpression Query String to search.
* @param searchSpec Spec for setting filters, raw query etc.
+ * @throws RuntimeException If an error occurred during the execution.
+ *
+ * @deprecated use AppSearchSession#query instead.
* @hide
*/
@NonNull
@@ -261,7 +385,7 @@ public class AppSearchManager {
// them in one big list.
AndroidFuture searchResultsFuture = new AndroidFuture<>();
try {
- mService.query(DEFAULT_DATABASE, queryExpression,
+ mService.query(DEFAULT_DATABASE_NAME, queryExpression,
searchSpec.getBundle(), searchResultsFuture);
} catch (RemoteException e) {
searchResultsFuture.completeExceptionally(e);
@@ -278,7 +402,7 @@ public class AppSearchManager {
}
/**
- * Deletes {@link GenericDocument}s by URI.
+ * Removes {@link GenericDocument}s by URI.
*
* You should not call this method directly; instead, use the {@code AppSearch#delete()} API
* provided by JetPack.
@@ -289,12 +413,24 @@ public class AppSearchManager {
* or a failed {@link AppSearchResult} otherwise. URIs that are not found will return a
* failed {@link AppSearchResult} with a result code of
* {@link AppSearchResult#RESULT_NOT_FOUND}.
+ * @throws RuntimeException If an error occurred during the execution.
+ *
+ * @deprecated use {@link AppSearchSession#removeByUri} instead.
*/
public AppSearchBatchResult removeByUri(@NonNull RemoveByUriRequest request) {
List uris = new ArrayList<>(request.getUris());
AndroidFuture future = new AndroidFuture<>();
try {
- mService.removeByUri(DEFAULT_DATABASE, request.getNamespace(), uris, future);
+ mService.removeByUri(DEFAULT_DATABASE_NAME, request.getNamespace(), uris,
+ new IAppSearchBatchResultCallback.Stub() {
+ public void onResult(AppSearchBatchResult result) {
+ future.complete(result);
+ }
+
+ public void onSystemError(ParcelableException exception) {
+ future.completeExceptionally(exception);
+ }
+ });
} catch (RemoteException e) {
future.completeExceptionally(e);
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl
new file mode 100644
index 0000000000000..f0b29964b895d
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ * 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;
+
+/** {@hide} */
+parcelable AppSearchResult;
\ No newline at end of file
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
index 979eab90a980c..6e2ed70cca016 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
@@ -19,9 +19,11 @@ package android.app.appsearch;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.appsearch.exceptions.AppSearchException;
import android.os.Parcel;
import android.os.Parcelable;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -218,4 +220,25 @@ public final class AppSearchResult implements Parcelable {
@ResultCode int resultCode, @Nullable String errorMessage) {
return new AppSearchResult<>(resultCode, /*resultValue=*/ null, errorMessage);
}
+
+ /** @hide */
+ @NonNull
+ public static AppSearchResult throwableToFailedResult(
+ @NonNull Throwable t) {
+ if (t instanceof AppSearchException) {
+ return ((AppSearchException) t).toAppSearchResult();
+ }
+
+ @AppSearchResult.ResultCode int resultCode;
+ if (t instanceof IllegalStateException) {
+ resultCode = AppSearchResult.RESULT_INTERNAL_ERROR;
+ } else if (t instanceof IllegalArgumentException) {
+ resultCode = AppSearchResult.RESULT_INVALID_ARGUMENT;
+ } else if (t instanceof IOException) {
+ resultCode = AppSearchResult.RESULT_IO_ERROR;
+ } else {
+ resultCode = AppSearchResult.RESULT_UNKNOWN_ERROR;
+ }
+ return AppSearchResult.newFailedResult(resultCode, t.toString());
+ }
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
new file mode 100644
index 0000000000000..531436ecaf0c4
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -0,0 +1,312 @@
+/*
+ * 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.
+ * 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.app.appsearch.exceptions.AppSearchException;
+import android.os.Bundle;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Represents a connection to an AppSearch storage system where {@link GenericDocument}s can be
+ * placed and queried.
+ * @hide
+ */
+public final class AppSearchSession {
+ private final String mDatabaseName;
+ private final IAppSearchManager mService;
+
+ static void createSearchSession(
+ @NonNull AppSearchManager.SearchContext searchContext,
+ @NonNull IAppSearchManager service,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer> callback) {
+ AppSearchSession searchSession =
+ new AppSearchSession(searchContext.mDatabaseName, service);
+ searchSession.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(AppSearchSession.this));
+ } else {
+ callback.accept(result);
+ }
+ });
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private AppSearchSession(@NonNull String databaseName, @NonNull IAppSearchManager service) {
+ mDatabaseName = databaseName;
+ mService = service;
+ }
+
+ /**
+ * Sets the schema will be used by documents provided to the {@link #putDocuments} method.
+ *
+ * The schema provided here is compared to the stored copy of the schema previously supplied
+ * to {@link #setSchema}, if any, to determine how to treat existing documents. The following
+ * types of schema modifications are always safe and are made without deleting any existing
+ * documents:
+ *
+ * - Addition of new types
+ *
- Addition of new
+ * {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or
+ * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} properties to a
+ * type
+ *
- Changing the cardinality of a data type to be less restrictive (e.g. changing an
+ * {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
+ * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} property.
+ *
+ *
+ * The following types of schema changes are not backwards-compatible:
+ *
+ * - Removal of an existing type
+ *
- Removal of a property from a type
+ *
- Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
+ *
- For properties of {@code Document} type, changing the schema type of
+ * {@code Document}s of that property
+ *
- Changing the cardinality of a data type to be more restrictive (e.g. changing an
+ * {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
+ * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property).
+ *
- Adding a
+ * {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property.
+ *
+ * Supplying a schema with such changes will, by default, result in this call returning an
+ * {@link AppSearchResult} with a code of {@link AppSearchResult#RESULT_INVALID_SCHEMA} and an
+ * error message describing the incompatibility. In this case the previously set schema will
+ * remain active.
+ *
+ *
If you need to make non-backwards-compatible changes as described above, you can set the
+ * {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. In this case,
+ * instead of returning an {@link AppSearchResult} with the
+ * {@link AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
+ * compatible with the new schema will be deleted and the incompatible schema will be applied.
+ *
+ *
It is a no-op to set the same schema as has been previously set; this is handled
+ * efficiently.
+ *
+ * @param request The schema update request.
+ * @param executor Executor on which to invoke the callback.
+ * @param callback Callback to receive errors resulting from setting the schema. If the
+ * operation succeeds, the callback will be invoked with {@code null}.
+ */
+ public void setSchema(
+ @NonNull SetSchemaRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer> callback) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ List schemaBundles = new ArrayList<>(request.getSchemas().size());
+ for (AppSearchSchema schema : request.getSchemas()) {
+ schemaBundles.add(schema.getBundle());
+ }
+ try {
+ mService.setSchema(mDatabaseName, schemaBundles, request.isForceOverride(),
+ new IAppSearchResultCallback.Stub() {
+ public void onResult(AppSearchResult result) {
+ executor.execute(() -> callback.accept(result));
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indexes documents into AppSearch.
+ *
+ * Each {@link GenericDocument}'s {@code schemaType} field must be set to the name of a
+ * schema type previously registered via the {@link #setSchema} method.
+ *
+ * @param request {@link PutDocumentsRequest} containing documents to be indexed
+ * @param executor Executor on which to invoke the callback.
+ * @param callback Callback to receive pending result of performing this operation. The keys
+ * of the returned {@link AppSearchBatchResult} are the URIs of the input
+ * documents. The values are {@code null} if they were successfully indexed,
+ * or a failed {@link AppSearchResult} otherwise.
+ * Or {@link BatchResultCallback#onSystemError} will be invoked with an
+ * {@link AppSearchException} if an error occurred in AppSearch initialization
+ * or a cause {@link Throwable} if other error occurred in AppSearch service.
+ */
+ public void putDocuments(
+ @NonNull PutDocumentsRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BatchResultCallback callback) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ List documents = request.getDocuments();
+ List documentBundles = new ArrayList<>(documents.size());
+ for (int i = 0; i < documents.size(); i++) {
+ documentBundles.add(documents.get(i).getBundle());
+ }
+ try {
+ mService.putDocuments(mDatabaseName, documentBundles,
+ new IAppSearchBatchResultCallback.Stub() {
+ public void onResult(AppSearchBatchResult result) {
+ executor.execute(() -> callback.onResult(result));
+ }
+
+ public void onSystemError(ParcelableException exception) {
+ executor.execute(() -> callback.onSystemError(exception.getCause()));
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieves {@link GenericDocument}s by URI.
+ *
+ * @param request {@link GetByUriRequest} containing URIs to be retrieved.
+ * @param executor Executor on which to invoke the callback.
+ * @param callback Callback to receive the pending result of performing this operation. The keys
+ * of the returned {@link AppSearchBatchResult} are the input URIs. The values
+ * are the returned {@link GenericDocument}s on success, or a failed
+ * {@link AppSearchResult} otherwise. URIs that are not found will return a
+ * failed {@link AppSearchResult} with a result code of
+ * {@link AppSearchResult#RESULT_NOT_FOUND}.
+ * Or {@link BatchResultCallback#onSystemError} will be invoked with an
+ * {@link AppSearchException} if an error occurred in AppSearch initialization
+ * or a cause {@link Throwable} if other error occurred in AppSearch service.
+ */
+ public void getByUri(
+ @NonNull GetByUriRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BatchResultCallback callback) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mService.getDocuments(mDatabaseName, request.getNamespace(),
+ new ArrayList<>(request.getUris()),
+ new IAppSearchBatchResultCallback.Stub() {
+ public void onResult(AppSearchBatchResult result) {
+ executor.execute(() -> {
+ AppSearchBatchResult.Builder
+ documentResultBuilder =
+ new AppSearchBatchResult.Builder<>();
+
+ // Translate successful results
+ for (Map.Entry bundleEntry :
+ (Set>)
+ result.getSuccesses().entrySet()) {
+ GenericDocument document;
+ try {
+ document = new GenericDocument(bundleEntry.getValue());
+ } catch (Throwable t) {
+ // These documents went through validation, so how could
+ // this fail? We must have done something wrong.
+ documentResultBuilder.setFailure(
+ bundleEntry.getKey(),
+ AppSearchResult.RESULT_INTERNAL_ERROR,
+ t.getMessage());
+ continue;
+ }
+ documentResultBuilder.setSuccess(
+ bundleEntry.getKey(), document);
+ }
+
+ // Translate failed results
+ for (Map.Entry> bundleEntry :
+ (Set>>)
+ result.getFailures().entrySet()) {
+ documentResultBuilder.setFailure(
+ bundleEntry.getKey(),
+ bundleEntry.getValue().getResultCode(),
+ bundleEntry.getValue().getErrorMessage());
+ }
+ callback.onResult(documentResultBuilder.build());
+ });
+ }
+
+ public void onSystemError(ParcelableException exception) {
+ executor.execute(() -> callback.onSystemError(exception.getCause()));
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes {@link GenericDocument}s from the index by URI.
+ *
+ * @param request Request containing URIs to be removed.
+ * @param executor Executor on which to invoke the callback.
+ * @param callback Callback to receive the pending result of performing this operation. The keys
+ * of the returned {@link AppSearchBatchResult} are the input URIs. The values
+ * are {@code null} on success, or a failed {@link AppSearchResult} otherwise.
+ * URIs that are not found will return a failed {@link AppSearchResult} with a
+ * result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
+ * Or {@link BatchResultCallback#onSystemError} will be invoked with an
+ * {@link AppSearchException} if an error occurred in AppSearch initialization
+ * or a cause {@link Throwable} if other error occurred in AppSearch service.
+ */
+ public void removeByUri(
+ @NonNull RemoveByUriRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BatchResultCallback callback) {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mService.removeByUri(mDatabaseName, request.getNamespace(),
+ new ArrayList<>(request.getUris()),
+ new IAppSearchBatchResultCallback.Stub() {
+ public void onResult(AppSearchBatchResult result) {
+ executor.execute(() -> callback.onResult(result));
+ }
+
+ public void onSystemError(ParcelableException exception) {
+ executor.execute(() -> callback.onSystemError(exception.getCause()));
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ // TODO(b/162450968) port query() and SearchResults.java to platform.
+ // TODO(b/162450968) port removeByQuery() to platform.
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java b/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
new file mode 100644
index 0000000000000..1689e02c7ce43
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+/**
+ * The callback interface to return {@link AppSearchBatchResult}.
+ *
+ * @param The type of the keys for {@link AppSearchBatchResult#getSuccesses} and
+ * {@link AppSearchBatchResult#getFailures}.
+ * @param The type of result objects associated with the keys.
+ * @hide
+ */
+public interface BatchResultCallback {
+
+ /**
+ * Called when {@link AppSearchBatchResult} results are ready.
+ *
+ * @param result The result of the executed request.
+ */
+ void onResult(AppSearchBatchResult result);
+
+
+ /**
+ * Called when a system error occurred.
+ *
+ * @param throwable The cause throwable.
+ */
+ default void onSystemError(Throwable throwable) {
+ if (throwable != null) {
+ throw new RuntimeException(throwable);
+ }
+ }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
new file mode 100644
index 0000000000000..b1bbd18b98e23
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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.
+ * 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.app.appsearch.AppSearchBatchResult;
+import android.os.ParcelableException;
+
+/** {@hide} */
+oneway interface IAppSearchBatchResultCallback {
+ void onResult(in AppSearchBatchResult result);
+ void onSystemError(in ParcelableException exception);
+}
\ No newline at end of file
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index 01260ea193f6a..4a981022da738 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -17,10 +17,12 @@ package android.app.appsearch;
import android.os.Bundle;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.IAppSearchBatchResultCallback;
+import android.app.appsearch.IAppSearchResultCallback;
import com.android.internal.infra.AndroidFuture;
-parcelable AppSearchResult;
-parcelable AppSearchBatchResult;
parcelable SearchResults;
/** {@hide} */
@@ -32,14 +34,14 @@ interface IAppSearchManager {
* @param schemaBundles List of AppSearchSchema bundles.
* @param forceOverride Whether to apply the new schema even if it is incompatible. All
* incompatible documents will be deleted.
- * @param callback {@link AndroidFuture}<{@link AppSearchResult}<{@link Void}>>.
- * The results of the call.
+ * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
+ * {@link AppSearchResult}<{@link Void}>.
*/
void setSchema(
in String databaseName,
in List schemaBundles,
boolean forceOverride,
- in AndroidFuture callback);
+ in IAppSearchResultCallback callback);
/**
* Inserts documents into the index.
@@ -47,16 +49,16 @@ interface IAppSearchManager {
* @param databaseName The name of the database where this document lives.
* @param documentBundes List of GenericDocument bundles.
* @param callback
- * {@link AndroidFuture}<{@link AppSearchBatchResult}<{@link String}, {@link Void}>>.
- * If the call fails to start, {@code callback} will be completed exceptionally. Otherwise,
- * {@code callback} will be completed with an
+ * If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError}
+ * will be called with the cause throwable. Otherwise,
+ * {@link IAppSearchBatchResultCallback#onResult} will be called with an
* {@link AppSearchBatchResult}<{@link String}, {@link Void}>
* where the keys are document URIs, and the values are {@code null}.
*/
void putDocuments(
in String databaseName,
in List documentBundles,
- in AndroidFuture callback);
+ in IAppSearchBatchResultCallback callback);
/**
* Retrieves documents from the index.
@@ -65,9 +67,9 @@ interface IAppSearchManager {
* @param namespace The namespace this document resides in.
* @param uris The URIs of the documents to retrieve
* @param callback
- * {@link AndroidFuture}<{@link AppSearchBatchResult}<{@link String}, {@link Bundle}>>.
- * If the call fails to start, {@code callback} will be completed exceptionally. Otherwise,
- * {@code callback} will be completed with an
+ * If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError}
+ * will be called with the cause throwable. Otherwise,
+ * {@link IAppSearchBatchResultCallback#onResult} will be called with an
* {@link AppSearchBatchResult}<{@link String}, {@link Bundle}>
* where the keys are document URIs, and the values are Document bundles.
*/
@@ -75,7 +77,7 @@ interface IAppSearchManager {
in String databaseName,
in String namespace,
in List uris,
- in AndroidFuture callback);
+ in IAppSearchBatchResultCallback callback);
/**
* Searches a document based on a given specifications.
@@ -98,9 +100,9 @@ interface IAppSearchManager {
* @param namespace Namespace of the document to remove.
* @param uris The URIs of the documents to delete
* @param callback
- * {@link AndroidFuture}<{@link AppSearchBatchResult}<{@link String}, {@link Void}>>.
- * If the call fails to start, {@code callback} will be completed exceptionally. Otherwise,
- * {@code callback} will be completed with an
+ * If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError}
+ * will be called with the cause throwable. Otherwise,
+ * {@link IAppSearchBatchResultCallback#onResult} will be called with an
* {@link AppSearchBatchResult}<{@link String}, {@link Void}>
* where the keys are document URIs. If a document doesn't exist, it will be reported as a
* failure where the {@code throwable} is {@code null}.
@@ -109,7 +111,7 @@ interface IAppSearchManager {
in String databaseName,
in String namespace,
in List uris,
- in AndroidFuture callback);
+ in IAppSearchBatchResultCallback callback);
/**
* Removes documents by given query.
@@ -124,4 +126,12 @@ interface IAppSearchManager {
in String queryExpression,
in Bundle searchSpecBundle,
in AndroidFuture callback);
+
+ /**
+ * Creates and initializes AppSearchImpl for the calling app.
+ *
+ * @param callback {@link IAppSearchResultCallback#onResult} will be called with an
+ * {@link AppSearchResult}<{@link Void}>.
+ */
+ void initialize(in IAppSearchResultCallback callback);
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
new file mode 100644
index 0000000000000..27729a5ad0584
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
@@ -0,0 +1,24 @@
+/**
+ * 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.
+ * 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.app.appsearch.AppSearchResult;
+import android.os.ParcelableException;
+
+/** {@hide} */
+oneway interface IAppSearchResultCallback {
+ void onResult(in AppSearchResult result);
+}
\ No newline at end of file
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 1dfde528e69b9..8269799d5a0d6 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -15,27 +15,32 @@
*/
package com.android.server.appsearch;
+import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
+
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
+import android.app.appsearch.IAppSearchBatchResultCallback;
import android.app.appsearch.IAppSearchManager;
+import android.app.appsearch.IAppSearchResultCallback;
import android.app.appsearch.SearchResultPage;
import android.app.appsearch.SearchSpec;
-import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
+import android.os.ParcelableException;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
-import java.io.IOException;
import java.util.List;
import java.util.Set;
@@ -61,10 +66,9 @@ public class AppSearchManagerService extends SystemService {
@NonNull String databaseName,
@NonNull List schemaBundles,
boolean forceOverride,
- @NonNull AndroidFuture callback) {
+ @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();
@@ -76,9 +80,10 @@ public class AppSearchManagerService extends SystemService {
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
impl.setSchema(databaseName, schemas, forceOverride);
- callback.complete(AppSearchResult.newSuccessfulResult(/*result=*/ null));
+ invokeCallbackOnResult(callback,
+ AppSearchResult.newSuccessfulResult(/*result=*/ null));
} catch (Throwable t) {
- callback.complete(throwableToFailedResult(t));
+ invokeCallbackOnError(callback, t);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -88,7 +93,7 @@ public class AppSearchManagerService extends SystemService {
public void putDocuments(
@NonNull String databaseName,
@NonNull List documentBundles,
- @NonNull AndroidFuture callback) {
+ @NonNull IAppSearchBatchResultCallback callback) {
Preconditions.checkNotNull(databaseName);
Preconditions.checkNotNull(documentBundles);
Preconditions.checkNotNull(callback);
@@ -96,22 +101,24 @@ public class AppSearchManagerService extends SystemService {
int callingUserId = UserHandle.getUserId(callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
- AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
- databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
AppSearchBatchResult.Builder resultBuilder =
new AppSearchBatchResult.Builder<>();
+ AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+ databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
for (int i = 0; i < documentBundles.size(); i++) {
GenericDocument document = new GenericDocument(documentBundles.get(i));
try {
+ // TODO(b/173451571): reduce burden of binder thread by enqueue request onto
+ // a separate thread.
impl.putDocument(databaseName, document);
resultBuilder.setSuccess(document.getUri(), /*result=*/ null);
} catch (Throwable t) {
resultBuilder.setResult(document.getUri(), throwableToFailedResult(t));
}
}
- callback.complete(resultBuilder.build());
+ invokeCallbackOnResult(callback, resultBuilder.build());
} catch (Throwable t) {
- callback.completeExceptionally(t);
+ invokeCallbackOnError(callback, t);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -119,7 +126,8 @@ public class AppSearchManagerService extends SystemService {
@Override
public void getDocuments(@NonNull String databaseName, @NonNull String namespace,
- @NonNull List uris, @NonNull AndroidFuture callback) {
+ @NonNull List uris,
+ @NonNull IAppSearchBatchResultCallback callback) {
Preconditions.checkNotNull(databaseName);
Preconditions.checkNotNull(namespace);
Preconditions.checkNotNull(uris);
@@ -128,10 +136,10 @@ public class AppSearchManagerService extends SystemService {
int callingUserId = UserHandle.getUserId(callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
- AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
- databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
AppSearchBatchResult.Builder resultBuilder =
new AppSearchBatchResult.Builder<>();
+ AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+ databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
for (int i = 0; i < uris.size(); i++) {
String uri = uris.get(i);
try {
@@ -141,9 +149,9 @@ public class AppSearchManagerService extends SystemService {
resultBuilder.setResult(uri, throwableToFailedResult(t));
}
}
- callback.complete(resultBuilder.build());
+ invokeCallbackOnResult(callback, resultBuilder.build());
} catch (Throwable t) {
- callback.completeExceptionally(t);
+ invokeCallbackOnError(callback, t);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -182,19 +190,19 @@ public class AppSearchManagerService extends SystemService {
@Override
public void removeByUri(@NonNull String databaseName, @NonNull String namespace,
- List uris, AndroidFuture callback) {
+ @NonNull List uris,
+ @NonNull IAppSearchBatchResultCallback callback) {
Preconditions.checkNotNull(databaseName);
- Preconditions.checkNotNull(namespace);
Preconditions.checkNotNull(uris);
Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
+ AppSearchBatchResult.Builder resultBuilder =
+ new AppSearchBatchResult.Builder<>();
try {
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
databaseName = rewriteDatabaseNameWithUid(databaseName, callingUid);
- AppSearchBatchResult.Builder resultBuilder =
- new AppSearchBatchResult.Builder<>();
for (int i = 0; i < uris.size(); i++) {
String uri = uris.get(i);
try {
@@ -204,9 +212,9 @@ public class AppSearchManagerService extends SystemService {
resultBuilder.setResult(uri, throwableToFailedResult(t));
}
}
- callback.complete(resultBuilder.build());
+ invokeCallbackOnResult(callback, resultBuilder.build());
} catch (Throwable t) {
- callback.completeExceptionally(t);
+ invokeCallbackOnError(callback, t);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -237,6 +245,21 @@ public class AppSearchManagerService extends SystemService {
}
}
+ @Override
+ public void initialize(@NonNull IAppSearchResultCallback callback) {
+ int callingUid = Binder.getCallingUidOrThrow();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ ImplInstanceManager.getInstance(getContext(), callingUserId);
+ invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
+ } catch (Throwable t) {
+ invokeCallbackOnError(callback, t);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
/**
* Rewrites the database name by adding a prefix of unique name for the given uid.
*
@@ -256,23 +279,51 @@ public class AppSearchManagerService extends SystemService {
return callingUidName + CALLING_NAME_DATABASE_DELIMITER + databaseName;
}
- private AppSearchResult throwableToFailedResult(
- @NonNull Throwable t) {
- if (t instanceof AppSearchException) {
- return ((AppSearchException) t).toAppSearchResult();
+ /** Invokes the {@link IAppSearchResultCallback} with the result. */
+ private void invokeCallbackOnResult(IAppSearchResultCallback callback,
+ AppSearchResult result) {
+ try {
+ callback.onResult(result);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to send result to the callback", e);
}
+ }
- @AppSearchResult.ResultCode int resultCode;
- if (t instanceof IllegalStateException) {
- resultCode = AppSearchResult.RESULT_INTERNAL_ERROR;
- } else if (t instanceof IllegalArgumentException) {
- resultCode = AppSearchResult.RESULT_INVALID_ARGUMENT;
- } else if (t instanceof IOException) {
- resultCode = AppSearchResult.RESULT_IO_ERROR;
- } else {
- resultCode = AppSearchResult.RESULT_UNKNOWN_ERROR;
+ /** Invokes the {@link IAppSearchBatchResultCallback} with the result. */
+ private void invokeCallbackOnResult(IAppSearchBatchResultCallback callback,
+ AppSearchBatchResult result) {
+ try {
+ callback.onResult(result);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to send result to the callback", e);
+ }
+ }
+
+ /**
+ * Invokes the {@link IAppSearchResultCallback} with an throwable.
+ *
+ * The throwable is convert to a {@link AppSearchResult};
+ */
+ private void invokeCallbackOnError(IAppSearchResultCallback callback, Throwable throwable) {
+ try {
+ callback.onResult(throwableToFailedResult(throwable));
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to send result to the callback", e);
+ }
+ }
+
+ /**
+ * Invokes the {@link IAppSearchBatchResultCallback} with an throwable.
+ *
+ *
The throwable is converted to {@link ParcelableException}.
+ */
+ private void invokeCallbackOnError(IAppSearchBatchResultCallback callback,
+ Throwable throwable) {
+ try {
+ callback.onSystemError(new ParcelableException(throwable));
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to send error to the callback", e);
}
- return AppSearchResult.newFailedResult(resultCode, t.getMessage());
}
}
}