Merge "Port the putDocuments() API to be synchronous."

This commit is contained in:
Alexander Dorokhine
2020-01-21 10:45:03 +00:00
committed by Android (Google) Code Review
4 changed files with 197 additions and 29 deletions

View File

@@ -0,0 +1,150 @@
/*
* 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.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import java.util.Collections;
import java.util.Map;
/**
* Provides access to multiple results from a batch operation accepting multiple inputs.
*
* @param <KeyType> The type of the keys for {@link #getResults} and {@link #getFailures}.
* @param <ValueType> The type of result objects associated with the keys.
* @hide
*/
public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
@NonNull private final Map<KeyType, ValueType> mResults;
@NonNull private final Map<KeyType, Throwable> mFailures;
private AppSearchBatchResult(
@NonNull Map<KeyType, ValueType> results, @NonNull Map<KeyType, Throwable> failures) {
mResults = results;
mFailures = failures;
}
private AppSearchBatchResult(@NonNull Parcel in) {
mResults = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
mFailures = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeMap(mResults);
dest.writeMap(mFailures);
}
/** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
public boolean isSuccess() {
return mFailures.isEmpty();
}
/**
* Returns a {@link Map} of all successful keys mapped to the results they produced.
*
* <p>The values of the {@link Map} may be {@code null}.
*/
@NonNull
public Map<KeyType, ValueType> getResults() {
return mResults;
}
/**
* Returns a {@link Map} of all failed keys mapped to a {@link Throwable} representing the cause
* of failure.
*
* <p>The values of the {@link Map} may be {@code null}.
*/
@NonNull
public Map<KeyType, Throwable> getFailures() {
return mFailures;
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<AppSearchBatchResult> CREATOR =
new Creator<AppSearchBatchResult>() {
@NonNull
@Override
public AppSearchBatchResult createFromParcel(@NonNull Parcel in) {
return new AppSearchBatchResult(in);
}
@NonNull
@Override
public AppSearchBatchResult[] newArray(int size) {
return new AppSearchBatchResult[size];
}
};
/**
* Creates a new {@link Builder} for this {@link AppSearchBatchResult}.
* @hide
*/
@NonNull
public static <KeyType, ValueType> Builder<KeyType, ValueType> newBuilder() {
return new Builder<>();
}
/**
* Builder for {@link AppSearchBatchResult} objects.
*
* @param <KeyType> The type of keys.
* @param <ValueType> The type of result objects associated with the keys.
* @hide
*/
public static final class Builder<KeyType, ValueType> {
@NonNull private final Map<KeyType, ValueType> mResults = new ArrayMap<>();
@NonNull private final Map<KeyType, Throwable> mFailures = new ArrayMap<>();
private Builder() {}
/**
* Registers that the {@code key} was processed successfully and associates it with
* {@code value}. Any previous mapping for a key, whether success or failure, is deleted.
*/
public Builder setSuccess(@NonNull KeyType key, @Nullable ValueType value) {
mResults.put(key, value);
mFailures.remove(key);
return this;
}
/**
* Registers that the {@code key} failed and associates it with {@code throwable}. Any
* previous mapping for a key, whether success or failure, is deleted.
*/
public Builder setFailure(@NonNull KeyType key, @Nullable Throwable throwable) {
mFailures.put(key, throwable);
mResults.remove(key);
return this;
}
/** Builds an {@link AppSearchBatchResult} from the contents of this {@link Builder}. */
@NonNull
public AppSearchBatchResult<KeyType, ValueType> build() {
return new AppSearchBatchResult<>(mResults, mFailures);
}
}
}

View File

@@ -29,12 +29,12 @@ import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.StatusProto;
import com.google.android.icing.protobuf.InvalidProtocolBufferException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* This class provides access to the centralized AppSearch index maintained by the system.
@@ -158,26 +158,25 @@ public class AppSearchManager {
* name of a schema type previously registered via the {@link #setSchema} method.
*
* @param documents {@link Document Documents} that need to be indexed.
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive errors. On success, it will be called with {@code null}.
* On failure, it will be called with a {@link Throwable} describing the failure.
* @return An {@link AppSearchBatchResult} mapping the document URIs to {@link Void} if they
* were successfully indexed, or a {@link Throwable} describing the failure if they could
* not be indexed.
* @hide
*/
public void putDocuments(
@NonNull List<Document> documents,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<? super Throwable> callback) {
AndroidFuture<Void> future = new AndroidFuture<>();
public AppSearchBatchResult<String, Void> putDocuments(@NonNull List<Document> documents) {
// TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in
// one big list.
List<byte[]> documentsBytes = new ArrayList<>(documents.size());
for (Document document : documents) {
// TODO(b/146386470) batching Document protos
try {
mService.putDocument(document.getProto().toByteArray(), future);
} catch (RemoteException e) {
future.completeExceptionally(e);
break;
}
documentsBytes.add(document.getProto().toByteArray());
}
// TODO(b/147614371) Fix error report for multiple documents.
future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>();
try {
mService.putDocuments(documentsBytes, future);
} catch (RemoteException e) {
future.completeExceptionally(e);
}
return getFutureOrThrow(future);
}
/**

View File

@@ -17,6 +17,8 @@ package android.app.appsearch;
import com.android.internal.infra.AndroidFuture;
parcelable AppSearchBatchResult;
/** {@hide} */
interface IAppSearchManager {
/**
@@ -32,14 +34,17 @@ interface IAppSearchManager {
void setSchema(in byte[] schemaBytes, boolean forceOverride, in AndroidFuture callback);
/**
* Inserts a document into the index.
* Inserts documents into the index.
*
* @param documentBytes serialized DocumentProto
* @param callback {@link AndroidFuture}&lt;{@link Void}&gt;. Will be completed with
* {@code null} upon successful completion of the put call, or completed exceptionally if
* put fails.
* @param documentsBytes {@link List}&lt;byte[]&gt; of serialized DocumentProtos.
* @param callback
* {@link AndroidFuture}&lt;{@link AppSearchBatchResult}&lt;{@link String}, {@link Void}&gt;&gt;.
* If the call fails to start, {@code callback} will be completed exceptionally. Otherwise,
* {@code callback} will be completed with an
* {@link AppSearchBatchResult}&lt;{@link String}, {@link Void}&gt;
* where the keys are document URIs, and the values are {@code null}.
*/
void putDocument(in byte[] documentBytes, in AndroidFuture callback);
void putDocuments(in List documentsBytes, in AndroidFuture<AppSearchBatchResult> callback);
/**
* Searches a document based on a given query string.

View File

@@ -16,6 +16,7 @@
package com.android.server.appsearch;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.IAppSearchManager;
import android.content.Context;
import android.os.Binder;
@@ -34,6 +35,8 @@ import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.protobuf.InvalidProtocolBufferException;
import java.util.List;
/**
* TODO(b/142567528): add comments when implement this class
*/
@@ -72,17 +75,28 @@ public class AppSearchManagerService extends SystemService {
}
@Override
public void putDocument(byte[] documentBytes, AndroidFuture callback) {
Preconditions.checkNotNull(documentBytes);
public void putDocuments(
List documentsBytes, AndroidFuture<AppSearchBatchResult> callback) {
Preconditions.checkNotNull(documentsBytes);
Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
long callingIdentity = Binder.clearCallingIdentity();
try {
DocumentProto document = DocumentProto.parseFrom(documentBytes);
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
impl.putDocument(callingUid, document);
callback.complete(null);
AppSearchBatchResult.Builder<String, Void> resultBuilder =
AppSearchBatchResult.newBuilder();
for (int i = 0; i < documentsBytes.size(); i++) {
byte[] documentBytes = (byte[]) documentsBytes.get(i);
DocumentProto document = DocumentProto.parseFrom(documentBytes);
try {
impl.putDocument(callingUid, document);
resultBuilder.setSuccess(document.getUri(), /*value=*/ null);
} catch (Throwable t) {
resultBuilder.setFailure(document.getUri(), t);
}
}
callback.complete(resultBuilder.build());
} catch (Throwable t) {
callback.completeExceptionally(t);
} finally {