/* * Copyright (C) 2021 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.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.AppSearchResult; import android.app.appsearch.GetSchemaResponse; import android.app.appsearch.InternalSetSchemaResponse; import android.app.appsearch.ParcelableUtil; import android.app.appsearch.SearchResultPage; import android.app.appsearch.SearchSuggestionResult; import android.app.appsearch.SetSchemaResponse.MigrationFailure; import android.app.appsearch.StorageInfo; import android.app.appsearch.annotation.CanIgnoreReturnValue; import android.app.appsearch.functions.ExecuteAppFunctionResponse; import android.app.appsearch.safeparcel.AbstractSafeParcelable; import android.app.appsearch.safeparcel.GenericDocumentParcel; import android.app.appsearch.safeparcel.SafeParcelable; import android.os.Parcel; import android.os.Parcelable; import java.util.List; import java.util.Objects; /** * Parcelable wrapper around {@link AppSearchResult}. * *

{@link AppSearchResult} can contain any value, including non-parcelable values. For the * specific case of sending {@link AppSearchResult} across Binder, this class wraps an {@link * AppSearchResult} that contains a parcelable type and provides parcelability of the whole * structure. * * @param The type of result object for successful calls. Must be a parcelable type. * @hide */ @SafeParcelable.Class(creator = "AppSearchResultParcelCreator", creatorIsFinal = false) public final class AppSearchResultParcel extends AbstractSafeParcelable { @NonNull @SuppressWarnings("rawtypes") public static final Parcelable.Creator CREATOR = new AppSearchResultParcelCreator() { @Override public AppSearchResultParcel createFromParcel(Parcel in) { // We pass the result we get from ParcelableUtil#readBlob to // AppSearchResultParcelCreator to decode. byte[] dataBlob = Objects.requireNonNull(ParcelableUtil.readBlob(in)); // Create a parcel object to un-serialize the byte array we are reading from // Parcel.readBlob(). Parcel.WriteBlob() could take care of whether to pass // data via binder directly or Android shared memory if the data is large. Parcel unmarshallParcel = Parcel.obtain(); try { unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length); unmarshallParcel.setDataPosition(0); return super.createFromParcel(unmarshallParcel); } finally { unmarshallParcel.recycle(); } } }; @NonNull private static final Parcelable.Creator CREATOR_WITHOUT_BLOB = new AppSearchResultParcelCreator(); @Field(id = 1) @AppSearchResult.ResultCode int mResultCode; @Field(id = 2) @Nullable String mErrorMessage; @Field(id = 3) @Nullable InternalSetSchemaResponse mInternalSetSchemaResponse; @Field(id = 4) @Nullable GetSchemaResponse mGetSchemaResponse; @Field(id = 5) @Nullable List mStrings; @Field(id = 6) @Nullable GenericDocumentParcel mGenericDocumentParcel; @Field(id = 7) @Nullable SearchResultPage mSearchResultPage; @Field(id = 8) @Nullable List mMigrationFailures; @Field(id = 9) @Nullable List mSearchSuggestionResults; @Field(id = 10) @Nullable StorageInfo mStorageInfo; @Field(id = 11) @Nullable ExecuteAppFunctionResponse mExecuteAppFunctionResponse; @NonNull AppSearchResult mResultCached; /** * Creates an AppSearchResultParcel for given value type. * * @param resultCode A {@link AppSearchResult} result code for {@link IAppSearchManager} API * response. * @param errorMessage An error message in case of a failed response. * @param internalSetSchemaResponse An {@link InternalSetSchemaResponse} type response. * @param getSchemaResponse An {@link GetSchemaResponse} type response. * @param strings An {@link List} type response. * @param genericDocumentParcel An {@link GenericDocumentParcel} type response. * @param searchResultPage An {@link SearchResultPage} type response. * @param migrationFailures An {@link List} type response. * @param searchSuggestionResults An {@link List} type response. * @param storageInfo {@link StorageInfo} type response. */ @Constructor AppSearchResultParcel( @Param(id = 1) @AppSearchResult.ResultCode int resultCode, @Param(id = 2) @Nullable String errorMessage, @Param(id = 3) @Nullable InternalSetSchemaResponse internalSetSchemaResponse, @Param(id = 4) @Nullable GetSchemaResponse getSchemaResponse, @Param(id = 5) @Nullable List strings, @Param(id = 6) @Nullable GenericDocumentParcel genericDocumentParcel, @Param(id = 7) @Nullable SearchResultPage searchResultPage, @Param(id = 8) @Nullable List migrationFailures, @Param(id = 9) @Nullable List searchSuggestionResults, @Param(id = 10) @Nullable StorageInfo storageInfo, @Param(id = 11) @Nullable ExecuteAppFunctionResponse executeAppFunctionResponse) { mResultCode = resultCode; mErrorMessage = errorMessage; if (resultCode == AppSearchResult.RESULT_OK) { mInternalSetSchemaResponse = internalSetSchemaResponse; mGetSchemaResponse = getSchemaResponse; mStrings = strings; mGenericDocumentParcel = genericDocumentParcel; mSearchResultPage = searchResultPage; mMigrationFailures = migrationFailures; mSearchSuggestionResults = searchSuggestionResults; mStorageInfo = storageInfo; mExecuteAppFunctionResponse = executeAppFunctionResponse; if (mInternalSetSchemaResponse != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mInternalSetSchemaResponse); } else if (mGetSchemaResponse != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mGetSchemaResponse); } else if (mStrings != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mStrings); } else if (mGenericDocumentParcel != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mGenericDocumentParcel); } else if (mSearchResultPage != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mSearchResultPage); } else if (mMigrationFailures != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mMigrationFailures); } else if (mSearchSuggestionResults != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mSearchSuggestionResults); } else if (mStorageInfo != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mStorageInfo); } else if (mExecuteAppFunctionResponse != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mExecuteAppFunctionResponse); } else { // Default case where code is OK and value is null. mResultCached = AppSearchResult.newSuccessfulResult(null); } } else { mResultCached = AppSearchResult.newFailedResult(mResultCode, mErrorMessage); } } /** * Creates a new {@link AppSearchResultParcel} from the given result in case a successful Void * response. */ public static AppSearchResultParcel fromVoid() { return new AppSearchResultParcel.Builder<>(AppSearchResult.RESULT_OK).build(); } /** Creates a new failed {@link AppSearchResultParcel} from result code and error message. */ @SuppressWarnings({"unchecked", "rawtypes"}) public static AppSearchResultParcel fromFailedResult(AppSearchResult failedResult) { if (failedResult.isSuccess()) { throw new IllegalStateException( "Creating a failed AppSearchResultParcel from a " + "successful response"); } return new AppSearchResultParcel.Builder<>(failedResult.getResultCode()) .setErrorMessage(failedResult.getErrorMessage()) .build(); } /** * Creates a new {@link AppSearchResultParcel} from the given result in case a successful {@link * InternalSetSchemaResponse}. */ public static AppSearchResultParcel fromInternalSetSchemaResponse( InternalSetSchemaResponse internalSetSchemaResponse) { return new AppSearchResultParcel.Builder( AppSearchResult.RESULT_OK) .setInternalSetSchemaResponse(internalSetSchemaResponse) .build(); } /** * Creates a new {@link AppSearchResultParcel} from the given result in case a successful {@link * GetSchemaResponse}. */ public static AppSearchResultParcel fromGetSchemaResponse( GetSchemaResponse getSchemaResponse) { return new AppSearchResultParcel.Builder(AppSearchResult.RESULT_OK) .setGetSchemaResponse(getSchemaResponse) .build(); } /** * Creates a new {@link AppSearchResultParcel} from the given result in case a successful {@link * List}<{@link String}>. */ public static AppSearchResultParcel> fromStringList(List stringList) { return new AppSearchResultParcel.Builder>(AppSearchResult.RESULT_OK) .setStrings(stringList) .build(); } /** * Creates a new {@link AppSearchResultParcel} from the given result in case a successful {@link * GenericDocumentParcel}. */ public static AppSearchResultParcel fromGenericDocumentParcel( GenericDocumentParcel genericDocumentParcel) { return new AppSearchResultParcel.Builder(AppSearchResult.RESULT_OK) .setGenericDocumentParcel(genericDocumentParcel) .build(); } /** * Creates a new {@link AppSearchResultParcel} from the given result in case a successful {@link * SearchResultPage}. */ public static AppSearchResultParcel fromSearchResultPage( SearchResultPage searchResultPage) { return new AppSearchResultParcel.Builder(AppSearchResult.RESULT_OK) .setSearchResultPage(searchResultPage) .build(); } /** * Creates a new {@link AppSearchResultParcel} from the given result in case a successful {@link * List}<{@link MigrationFailure}>. */ public static AppSearchResultParcel> fromMigrationFailuresList( List migrationFailureList) { return new AppSearchResultParcel.Builder>(AppSearchResult.RESULT_OK) .setMigrationFailures(migrationFailureList) .build(); } /** * Creates a new {@link AppSearchResultParcel} from the given result in case a successful {@link * List}<{@link SearchSuggestionResult}>. */ public static AppSearchResultParcel> fromSearchSuggestionResultList( List searchSuggestionResultList) { return new AppSearchResultParcel.Builder>( AppSearchResult.RESULT_OK) .setSearchSuggestionResults(searchSuggestionResultList) .build(); } /** * Creates a new {@link AppSearchResultParcel} from the given result in case a successful {@link * StorageInfo}. */ public static AppSearchResultParcel fromStorageInfo(StorageInfo storageInfo) { return new AppSearchResultParcel.Builder(AppSearchResult.RESULT_OK) .setStorageInfo(storageInfo) .build(); } /** * Creates a new {@link AppSearchResultParcel} from the given result in case a successful {@link * ExecuteAppFunctionResponse}. */ public static AppSearchResultParcel fromExecuteAppFunctionResponse( ExecuteAppFunctionResponse executeAppFunctionResponse) { return new AppSearchResultParcel.Builder( AppSearchResult.RESULT_OK) .setExecuteAppFunctionResponse(executeAppFunctionResponse) .build(); } @NonNull public AppSearchResult getResult() { return mResultCached; } /** @hide */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { // Serializes the whole object, So that we can use Parcel.writeBlob() to send data. // WriteBlob() could take care of whether to pass data via binder directly or Android shared // memory if the data is large. byte[] bytes; Parcel data = Parcel.obtain(); try { // We pass encoded result from AppSearchResultParcelCreator to ParcelableUtil#writeBlob. directlyWriteToParcel(this, data, flags); bytes = data.marshall(); } finally { data.recycle(); } ParcelableUtil.writeBlob(dest, bytes); } static void directlyWriteToParcel( @NonNull AppSearchResultParcel result, @NonNull Parcel data, int flags) { AppSearchResultParcelCreator.writeToParcel(result, data, flags); } static AppSearchResultParcel directlyReadFromParcel(@NonNull Parcel data) { return CREATOR_WITHOUT_BLOB.createFromParcel(data); } /** * Builder for {@link AppSearchResultParcel} objects. * * @param The type of the result objects for successful results. */ static final class Builder { @AppSearchResult.ResultCode private final int mResultCode; @Nullable private String mErrorMessage; @Nullable private InternalSetSchemaResponse mInternalSetSchemaResponse; @Nullable private GetSchemaResponse mGetSchemaResponse; @Nullable private List mStrings; @Nullable private GenericDocumentParcel mGenericDocumentParcel; @Nullable private SearchResultPage mSearchResultPage; @Nullable private List mMigrationFailures; @Nullable private List mSearchSuggestionResults; @Nullable private StorageInfo mStorageInfo; @Nullable private ExecuteAppFunctionResponse mExecuteAppFunctionResponse; /** Builds an {@link AppSearchResultParcel.Builder}. */ Builder(int resultCode) { mResultCode = resultCode; } @CanIgnoreReturnValue Builder setErrorMessage(@Nullable String errorMessage) { mErrorMessage = errorMessage; return this; } @CanIgnoreReturnValue Builder setInternalSetSchemaResponse( InternalSetSchemaResponse internalSetSchemaResponse) { mInternalSetSchemaResponse = internalSetSchemaResponse; return this; } @CanIgnoreReturnValue Builder setGetSchemaResponse(GetSchemaResponse getSchemaResponse) { mGetSchemaResponse = getSchemaResponse; return this; } @CanIgnoreReturnValue Builder setStrings(List strings) { mStrings = strings; return this; } @CanIgnoreReturnValue Builder setGenericDocumentParcel(GenericDocumentParcel genericDocumentParcel) { mGenericDocumentParcel = genericDocumentParcel; return this; } @CanIgnoreReturnValue Builder setSearchResultPage(SearchResultPage searchResultPage) { mSearchResultPage = searchResultPage; return this; } @CanIgnoreReturnValue Builder setMigrationFailures(List migrationFailures) { mMigrationFailures = migrationFailures; return this; } @CanIgnoreReturnValue Builder setSearchSuggestionResults( List searchSuggestionResults) { mSearchSuggestionResults = searchSuggestionResults; return this; } @CanIgnoreReturnValue Builder setStorageInfo(StorageInfo storageInfo) { mStorageInfo = storageInfo; return this; } @CanIgnoreReturnValue Builder setExecuteAppFunctionResponse( ExecuteAppFunctionResponse executeAppFunctionResponse) { mExecuteAppFunctionResponse = executeAppFunctionResponse; return this; } /** * Builds an {@link AppSearchResultParcel} object from the contents of this {@link * AppSearchResultParcel.Builder}. */ @NonNull AppSearchResultParcel build() { return new AppSearchResultParcel<>( mResultCode, mErrorMessage, mInternalSetSchemaResponse, mGetSchemaResponse, mStrings, mGenericDocumentParcel, mSearchResultPage, mMigrationFailures, mSearchSuggestionResults, mStorageInfo, mExecuteAppFunctionResponse); } } }