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

Commit 93d53122 authored by Alexander Dorokhine's avatar Alexander Dorokhine
Browse files

Switch AppSearchBatchResult from Throwable to error code and message.

Based on API review feedback:
> A map of Throwable objects is very unusual. Couldn't you return a
> structure of error code and reason string? If you know of specific
> error types, it's better to be explicit rather than have to parse
> just an exception string.

Bug: 143789408
Test: atest CtsAppSearchTestCases FrameworksCoreTests:android.app.appsearch FrameworksServicesTests:com.android.server.appsearch.impl
Change-Id: I8d3df1a176fd275db54783b387602ceb7b0d280a
parent 45b85518
Loading
Loading
Loading
Loading
+41 −40
Original line number Diff line number Diff line
@@ -26,30 +26,32 @@ import java.util.Collections;
import java.util.Map;

/**
 * Provides access to multiple results from a batch operation accepting multiple inputs.
 * Provides access to multiple {@link AppSearchResult}s from a batch operation accepting multiple
 * inputs.
 *
 * @param <KeyType> The type of the keys for {@link #getResults} and {@link #getFailures}.
 * @param <KeyType> The type of the keys for {@link #getSuccesses} 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;
    @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mSuccesses;
    @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mFailures;

    private AppSearchBatchResult(
            @NonNull Map<KeyType, ValueType> results, @NonNull Map<KeyType, Throwable> failures) {
        mResults = results;
            @NonNull Map<KeyType, AppSearchResult<ValueType>> successes,
            @NonNull Map<KeyType, AppSearchResult<ValueType>> failures) {
        mSuccesses = successes;
        mFailures = failures;
    }

    private AppSearchBatchResult(@NonNull Parcel in) {
        mResults = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
        mSuccesses = 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(mSuccesses);
        dest.writeMap(mFailures);
    }

@@ -59,23 +61,24 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
    }

    /**
     * Returns a {@link Map} of all successful keys mapped to the results they produced.
     * Returns a {@link Map} of all successful keys mapped to the successful
     * {@link AppSearchResult}s they produced.
     *
     * <p>The values of the {@link Map} may be {@code null}.
     * <p>The values of the {@link Map} will not be {@code null}.
     */
    @NonNull
    public Map<KeyType, ValueType> getResults() {
        return mResults;
    public Map<KeyType, AppSearchResult<ValueType>> getSuccesses() {
        return mSuccesses;
    }

    /**
     * Returns a {@link Map} of all failed keys mapped to a {@link Throwable} representing the cause
     * of failure.
     * Returns a {@link Map} of all failed keys mapped to the failed {@link AppSearchResult}s they
     * produced.
     *
     * <p>The values of the {@link Map} may be {@code null}.
     * <p>The values of the {@link Map} will not be {@code null}.
     */
    @NonNull
    public Map<KeyType, Throwable> getFailures() {
    public Map<KeyType, AppSearchResult<ValueType>> getFailures() {
        return mFailures;
    }

@@ -99,15 +102,6 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
        }
    };

    /**
     * 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.
     *
@@ -116,35 +110,42 @@ public class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
     * @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 final Map<KeyType, AppSearchResult<ValueType>> mSuccesses = new ArrayMap<>();
        private final Map<KeyType, AppSearchResult<ValueType>> mFailures = new ArrayMap<>();

        private Builder() {}
        /** Creates a new {@link Builder} for this {@link AppSearchBatchResult}. */
        public 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.
         * Associates the {@code key} with the given successful return value.
         *
         * <p>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;
        public Builder setSuccess(@NonNull KeyType key, @Nullable ValueType result) {
            return setResult(key, AppSearchResult.newSuccessfulResult(result));
        }

        /**
         * Registers that the {@code key} failed and associates it with {@code throwable}. Any
         * previous mapping for a key, whether success or failure, is deleted.
         * Associates the {@code key} with the given {@code result}.
         *
         * <p>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);
        @NonNull
        public Builder setResult(@NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) {
            if (result.isSuccess()) {
                mSuccesses.put(key, result);
                mFailures.remove(key);
            } else {
                mFailures.put(key, result);
                mSuccesses.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);
            return new AppSearchBatchResult<>(mSuccesses, mFailures);
        }
    }
}
+19 −15
Original line number Diff line number Diff line
@@ -75,9 +75,7 @@ public class AppSearchManager {
     *             REPEATED} property.
     * </ul>
     *
     * <p>The following types of schema changes are not backwards-compatible. Supplying a schema
     * with such changes will result in this call throwing an {@link IllegalSchemaException}
     * describing the incompatibility, and the previously set schema will remain active:
     * <p>The following types of schema changes are not backwards-compatible:
     * <ul>
     *     <li>Removal of an existing type
     *     <li>Removal of a property from a type
@@ -93,6 +91,10 @@ public class AppSearchManager {
     *         {@link android.app.appsearch.AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED
     *             REQUIRED} property.
     * </ul>
     * <p>Supplying a schema with such changes will 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.
     *
     * <p>If you need to make non-backwards-compatible changes as described above, instead use the
     * {@link #setSchema(List, boolean)} method with the {@code forceOverride} parameter set to
@@ -102,13 +104,13 @@ public class AppSearchManager {
     * efficiently.
     *
     * @param schemas The schema configs for the types used by the calling app.
     * @throws IllegalSchemaException If the provided schema is invalid, or is incompatible with the
     *     previous schema.
     * @return the result of performing this operation.
     *
     * @hide
     */
    public void setSchema(@NonNull AppSearchSchema... schemas) {
        setSchema(Arrays.asList(schemas), /*forceOverride=*/false);
    @NonNull
    public AppSearchResult<Void> setSchema(@NonNull AppSearchSchema... schemas) {
        return setSchema(Arrays.asList(schemas), /*forceOverride=*/false);
    }

    /**
@@ -116,20 +118,22 @@ public class AppSearchManager {
     *
     * <p>This method is similar to {@link #setSchema(AppSearchSchema...)}, except for the
     * {@code forceOverride} parameter. If a backwards-incompatible schema is specified but the
     * {@code forceOverride} parameter is set to {@code true}, instead of throwing an
     * {@link IllegalSchemaException}, all documents which are not compatible with the new schema
     * will be deleted and the incompatible schema will be applied.
     * {@code forceOverride} parameter is set to {@code true}, instead of returning an
     * {@link AppSearchResult} with the {@link AppSearchResult#RESULT_INVALID_SCHEMA} code, all
     * documents which are not compatible with the new schema will be deleted and the incompatible
     * schema will be applied.
     *
     * @param schemas The schema configs for the types used by the calling app.
     * @param forceOverride Whether to force the new schema to be applied even if there are
     *     incompatible changes versus the previously set schema. Documents which are incompatible
     *     with the new schema will be deleted.
     * @throws IllegalSchemaException If the provided schema is invalid, or is incompatible with the
     *     previous schema and the {@code forceOverride} parameter is set to {@code false}.
     * @return the result of performing this operation.
     *
     * @hide
     */
    public void setSchema(@NonNull List<AppSearchSchema> schemas, boolean forceOverride) {
    @NonNull
    public AppSearchResult<Void> setSchema(
            @NonNull List<AppSearchSchema> schemas, boolean forceOverride) {
        // Prepare the merged schema for transmission.
        SchemaProto.Builder schemaProtoBuilder = SchemaProto.newBuilder();
        for (AppSearchSchema schema : schemas) {
@@ -140,13 +144,13 @@ public class AppSearchManager {
        // TODO: This should use com.android.internal.infra.RemoteStream or another mechanism to
        //  avoid binder limits.
        byte[] schemaBytes = schemaProtoBuilder.build().toByteArray();
        AndroidFuture<Void> future = new AndroidFuture<>();
        AndroidFuture<AppSearchResult> future = new AndroidFuture<>();
        try {
            mService.setSchema(schemaBytes, forceOverride, future);
        } catch (RemoteException e) {
            future.completeExceptionally(e);
        }
        getFutureOrThrow(future);
        return getFutureOrThrow(future);
    }

    /**
+216 −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.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/**
 * Information about the success or failure of an AppSearch call.
 *
 * @param <ValueType> The type of result object for successful calls.
 * @hide
 */
public class AppSearchResult<ValueType> implements Parcelable {
    /** Result codes from {@link AppSearchManager} methods. */
    @IntDef(prefix = {"RESULT_"}, value = {
            RESULT_OK,
            RESULT_UNKNOWN_ERROR,
            RESULT_INTERNAL_ERROR,
            RESULT_INVALID_ARGUMENT,
            RESULT_IO_ERROR,
            RESULT_OUT_OF_SPACE,
            RESULT_NOT_FOUND,
            RESULT_INVALID_SCHEMA,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ResultCode {}

    /** The call was successful. */
    public static final int RESULT_OK = 0;

    /** An unknown error occurred while processing the call. */
    public static final int RESULT_UNKNOWN_ERROR = 1;

    /**
     * An internal error occurred within AppSearch, which the caller cannot address.
     *
     *
     * This error may be considered similar to {@link IllegalStateException}
     */
    public static final int RESULT_INTERNAL_ERROR = 2;

    /**
     * The caller supplied invalid arguments to the call.
     *
     * This error may be considered similar to {@link IllegalArgumentException}.
     */
    public static final int RESULT_INVALID_ARGUMENT = 3;

    /**
     * An issue occurred reading or writing to storage. The call might succeed if repeated.
     *
     * This error may be considered similar to {@link java.io.IOException}.
     */
    public static final int RESULT_IO_ERROR = 4;

    /** Storage is out of space, and no more space could be reclaimed. */
    public static final int RESULT_OUT_OF_SPACE = 5;

    /** An entity the caller requested to interact with does not exist in the system. */
    public static final int RESULT_NOT_FOUND = 6;

    /** The caller supplied a schema which is invalid or incompatible with the previous schema. */
    public static final int RESULT_INVALID_SCHEMA = 7;

    private final @ResultCode int mResultCode;
    @Nullable private final ValueType mResultValue;
    @Nullable private final String mErrorMessage;

    private AppSearchResult(
            @ResultCode int resultCode,
            @Nullable ValueType resultValue,
            @Nullable String errorMessage) {
        mResultCode = resultCode;
        mResultValue = resultValue;
        mErrorMessage = errorMessage;
    }

    private AppSearchResult(@NonNull Parcel in) {
        mResultCode = in.readInt();
        mResultValue = (ValueType) in.readValue(/*loader=*/ null);
        mErrorMessage = in.readString();
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mResultCode);
        dest.writeValue(mResultValue);
        dest.writeString(mErrorMessage);
    }

    /** Returns {@code true} if {@link #getResultCode} equals {@link AppSearchResult#RESULT_OK}. */
    public boolean isSuccess() {
        return getResultCode() == RESULT_OK;
    }

    /** Returns one of the {@code RESULT} constants defined in {@link AppSearchResult}. */
    public @ResultCode int getResultCode() {
        return mResultCode;
    }

    /**
     * Returns the returned value associated with this result.
     *
     * <p>If {@link #isSuccess} is {@code false}, the result value is always {@code null}. The value
     * may be {@code null} even if {@link #isSuccess} is {@code true}. See the documentation of the
     * particular {@link AppSearchManager} call producing this {@link AppSearchResult} for what is
     * returned by {@link #getResultValue}.
     */
    @Nullable
    public ValueType getResultValue() {
        return mResultValue;
    }

    /**
     * Returns the error message associated with this result.
     *
     * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
     * message may be {@code null} even if {@link #isSuccess} is {@code false}. See the
     * documentation of the particular {@link AppSearchManager} call producing this
     * {@link AppSearchResult} for what is returned by {@link #getErrorMessage}.
     */
    @Nullable
    public String getErrorMessage() {
        return mErrorMessage;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof AppSearchResult)) {
            return false;
        }
        AppSearchResult<?> otherResult = (AppSearchResult) other;
        return mResultCode == otherResult.mResultCode
                && Objects.equals(mResultValue, otherResult.mResultValue)
                && Objects.equals(mErrorMessage, otherResult.mErrorMessage);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mResultCode, mResultValue, mErrorMessage);
    }

    @Override
    @NonNull
    public String toString() {
        if (isSuccess()) {
            return "AppSearchResult [SUCCESS]: " + mResultValue;
        }
        return "AppSearchResult [FAILURE(" + mResultCode + ")]: " + mErrorMessage;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<AppSearchResult> CREATOR =
            new Creator<AppSearchResult>() {
        @NonNull
        @Override
        public AppSearchResult createFromParcel(@NonNull Parcel in) {
            return new AppSearchResult(in);
        }

        @NonNull
        @Override
        public AppSearchResult[] newArray(int size) {
            return new AppSearchResult[size];
        }
    };

    /**
     * Creates a new successful {@link AppSearchResult}.
     * @hide
     */
    @NonNull
    public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult(
            @Nullable ValueType value) {
        return new AppSearchResult<>(RESULT_OK, value, /*errorMessage=*/ null);
    }

    /**
     * Creates a new failed {@link AppSearchResult}.
     * @hide
     */
    @NonNull
    public static <ValueType> AppSearchResult<ValueType> newFailedResult(
            @ResultCode int resultCode, @Nullable String errorMessage) {
        return new AppSearchResult<>(resultCode, /*resultValue=*/ null, errorMessage);
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package android.app.appsearch;

import com.android.internal.infra.AndroidFuture;

parcelable AppSearchResult;
parcelable AppSearchBatchResult;

/** {@hide} */
@@ -27,11 +28,11 @@ interface IAppSearchManager {
     * @param schemaBytes Serialized SchemaProto.
     * @param forceOverride Whether to apply the new schema even if it is incompatible. All
     *     incompatible documents will be deleted.
     * @param callback {@link AndroidFuture}&lt;{@link Void}&gt;. Will be completed with
     *     {@code null} upon successful completion of the setSchema call, or completed
     *     exceptionally if setSchema fails.
     * @param callback {@link AndroidFuture}&lt;{@link AppSearchResult}&lt;{@link Void}&gt&gt;.
     *     The results of the call.
     */
    void setSchema(in byte[] schemaBytes, boolean forceOverride, in AndroidFuture callback);
    void setSchema(
        in byte[] schemaBytes, boolean forceOverride, in AndroidFuture<AppSearchResult> callback);

    /**
     * Inserts documents into the index.
+57 −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 com.android.server.appsearch;

import android.annotation.Nullable;
import android.app.appsearch.AppSearchResult;

/**
 * An exception thrown by {@link com.android.server.appsearch.AppSearchManagerService} or a
 * subcomponent.
 *
 * <p>These exceptions can be converted into a failed {@link android.app.appsearch.AppSearchResult}
 * for propagating to the client.
 */
public class AppSearchException extends Exception {
    private final @AppSearchResult.ResultCode int mResultCode;

    /** Initializes an {@link com.android.server.appsearch.AppSearchException} with no message. */
    public AppSearchException(@AppSearchResult.ResultCode int resultCode) {
        this(resultCode, /*message=*/ null);
    }

    public AppSearchException(
            @AppSearchResult.ResultCode int resultCode, @Nullable String message) {
        this(resultCode, message, /*cause=*/ null);
    }

    public AppSearchException(
            @AppSearchResult.ResultCode int resultCode,
            @Nullable String message,
            @Nullable Throwable cause) {
        super(message, cause);
        mResultCode = resultCode;
    }

    /**
     * Converts this {@link java.lang.Exception} into a failed
     * {@link android.app.appsearch.AppSearchResult}
     */
    public <T> AppSearchResult<T> toAppSearchResult() {
        return AppSearchResult.newFailedResult(mResultCode, getMessage());
    }
}
Loading