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

Commit 1fb565c8 authored by Alexander Dorokhine's avatar Alexander Dorokhine Committed by Android (Google) Code Review
Browse files

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

parents 61d530af 46e0337f
Loading
Loading
Loading
Loading
+150 −0
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}
+17 −18
Original line number Diff line number Diff line
@@ -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
            documentsBytes.add(document.getProto().toByteArray());
        }
        AndroidFuture<AppSearchBatchResult> future = new AndroidFuture<>();
        try {
                mService.putDocument(document.getProto().toByteArray(), future);
            mService.putDocuments(documentsBytes, future);
        } catch (RemoteException e) {
            future.completeExceptionally(e);
                break;
            }
        }
        // TODO(b/147614371) Fix error report for multiple documents.
        future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
        return getFutureOrThrow(future);
    }

    /**
+11 −6
Original line number Diff line number Diff line
@@ -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.
+19 −5
Original line number Diff line number Diff line
@@ -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);
                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);
                callback.complete(null);
                        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 {