/* * 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 static android.app.appsearch.ParcelableUtil.WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL; import static android.app.appsearch.ParcelableUtil.WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB; import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.AppSearchResult; import android.app.appsearch.CommitBlobResponse; import android.app.appsearch.OpenBlobForReadResponse; import android.app.appsearch.OpenBlobForWriteResponse; import android.app.appsearch.ParcelableUtil; import android.app.appsearch.RemoveBlobResponse; import android.app.appsearch.annotation.CanIgnoreReturnValue; import android.app.appsearch.safeparcel.AbstractSafeParcelable; import android.app.appsearch.safeparcel.SafeParcelable; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; 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. * *

Compare to deprecated {@link AppSearchResultParcel}, this class could config how to write it * to the parcel. Therefore binder objects and {@link ParcelFileDescriptor} is supported in this * class. * * @see ParcelableUtil#WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB * @see ParcelableUtil#WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL * @param The type of result object for successful calls. Must be a parcelable type. * @hide */ @SafeParcelable.Class(creator = "AppSearchResultParcelV2Creator", creatorIsFinal = false) public final class AppSearchResultParcelV2 extends AbstractSafeParcelable { private static final String TAG = "AppSearchResultParcelV2"; @NonNull @SuppressWarnings("rawtypes") public static final Parcelable.Creator CREATOR = new AppSearchResultParcelV2Creator() { @Override public AppSearchResultParcelV2 createFromParcel(Parcel in) { int writeParcelModel = in.readInt(); switch (writeParcelModel) { case WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB: byte[] dataBlob = Objects.requireNonNull(ParcelableUtil.readBlob(in)); Parcel unmarshallParcel = Parcel.obtain(); try { unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length); unmarshallParcel.setDataPosition(0); return directlyReadFromParcel(unmarshallParcel); } finally { unmarshallParcel.recycle(); } case WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL: return directlyReadFromParcel(in); default: throw new UnsupportedOperationException( "Cannot read AppSearchResultParcelV2 from Parcel with " + "unknown model: " + writeParcelModel); } } }; @NonNull private static final Parcelable.Creator CREATOR_WITHOUT_BLOB = new AppSearchResultParcelV2Creator(); static AppSearchResultParcelV2 directlyReadFromParcel(@NonNull Parcel data) { return CREATOR_WITHOUT_BLOB.createFromParcel(data); } static void directlyWriteToParcel( @NonNull AppSearchResultParcelV2 result, @NonNull Parcel data, int flags) { AppSearchResultParcelV2Creator.writeToParcel(result, data, flags); } /** * The flags indicate how we write this object to parcel and read it. * * @see ParcelableUtil#WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB * @see ParcelableUtil#WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL */ @Field(id = 1) @ParcelableUtil.WriteParcelMode int mWriteParcelMode; @Field(id = 2) @AppSearchResult.ResultCode int mResultCode; @Field(id = 3) @Nullable String mErrorMessage; @Field(id = 4) @Nullable ParcelFileDescriptor mParcelFileDescriptor; @Field(id = 5) @Nullable OpenBlobForWriteResponse mOpenBlobForWriteResponse; @Field(id = 6) @Nullable CommitBlobResponse mCommitBlobResponse; @Field(id = 7) @Nullable OpenBlobForReadResponse mOpenBlobForReadResponse; @Field(id = 8) @Nullable RemoveBlobResponse mRemoveBlobResponse; @NonNull AppSearchResult mResultCached; /** * Creates an AppSearchResultParcelV2 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. */ @Constructor AppSearchResultParcelV2( @Param(id = 1) int writeParcelMode, @Param(id = 2) @AppSearchResult.ResultCode int resultCode, @Param(id = 3) @Nullable String errorMessage, @Param(id = 4) @Nullable ParcelFileDescriptor parcelFileDescriptor, @Param(id = 5) @Nullable OpenBlobForWriteResponse openBlobForWriteResponse, @Param(id = 6) @Nullable CommitBlobResponse commitBlobResponse, @Param(id = 7) @Nullable OpenBlobForReadResponse openBlobForReadResponse, @Param(id = 8) @Nullable RemoveBlobResponse removeBlobResponse) { mWriteParcelMode = writeParcelMode; mResultCode = resultCode; mErrorMessage = errorMessage; if (resultCode == AppSearchResult.RESULT_OK) { mParcelFileDescriptor = parcelFileDescriptor; mOpenBlobForWriteResponse = openBlobForWriteResponse; mCommitBlobResponse = commitBlobResponse; mOpenBlobForReadResponse = openBlobForReadResponse; mRemoveBlobResponse = removeBlobResponse; if (mParcelFileDescriptor != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mParcelFileDescriptor); } else if (mOpenBlobForWriteResponse != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mOpenBlobForWriteResponse); } else if (mCommitBlobResponse != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mCommitBlobResponse); } else if (mOpenBlobForReadResponse != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mOpenBlobForReadResponse); } else if (mRemoveBlobResponse != null) { mResultCached = (AppSearchResult) AppSearchResult.newSuccessfulResult(mRemoveBlobResponse); } 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 AppSearchResultParcelV2} from the given result in case a successful Void * response. */ public static AppSearchResultParcelV2 fromVoid() { // We can marshall a void results, but since it is always a small object, we can directly // write it to parcel. return new AppSearchResultParcelV2.Builder<>( WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, AppSearchResult.RESULT_OK) .build(); } /** Creates a new failed {@link AppSearchResultParcelV2} from result code and error message. */ @SuppressWarnings({"unchecked", "rawtypes"}) public static AppSearchResultParcelV2 fromFailedResult(AppSearchResult failedResult) { if (failedResult.isSuccess()) { throw new IllegalStateException( "Creating a failed AppSearchResultParcelV2 from a " + "successful response"); } // We can marshall a failed results, but since it is always a small object, we can directly // write it to parcel. return new AppSearchResultParcelV2.Builder<>( WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, failedResult.getResultCode()) .setErrorMessage(failedResult.getErrorMessage()) .build(); } /** * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful * {@link ParcelFileDescriptor}. */ public static AppSearchResultParcelV2 fromParcelFileDescriptor( ParcelFileDescriptor parcelFileDescriptor) { // We CANNOT marshall a FD, we have to directly write it to parcel. return new AppSearchResultParcelV2.Builder( WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, AppSearchResult.RESULT_OK) .setParcelFileDescriptor(parcelFileDescriptor) .build(); } /** * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful * {@link OpenBlobForWriteResponse}. */ public static AppSearchResultParcelV2 fromOpenBlobForWriteResponse( OpenBlobForWriteResponse openBlobForWriteResponse) { // We CANNOT marshall OpenBlobForWriteResponse, since it contains FD, we have to directly // write it to parcel. return new AppSearchResultParcelV2.Builder( WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, AppSearchResult.RESULT_OK) .setOpenBlobForWriteResponse(openBlobForWriteResponse) .build(); } /** * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful * {@link RemoveBlobResponse}. */ public static AppSearchResultParcelV2 fromRemoveBlobResponseParcel( RemoveBlobResponse removeBlobResponse) { return new AppSearchResultParcelV2.Builder( WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB, AppSearchResult.RESULT_OK) .setRemoveBlobResponse(removeBlobResponse) .build(); } /** * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful * {@link CommitBlobResponse}. */ public static AppSearchResultParcelV2 fromCommitBlobResponseParcel( CommitBlobResponse commitBlobResponse) { return new AppSearchResultParcelV2.Builder( WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB, AppSearchResult.RESULT_OK) .setCommitBlobResponse(commitBlobResponse) .build(); } /** * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful * {@link OpenBlobForReadResponse}. */ public static AppSearchResultParcelV2 fromOpenBlobForReadResponse( OpenBlobForReadResponse OpenBlobForReadResponse) { // We CANNOT marshall OpenBlobForReadResponse, since it contains FD, we have to directly // write it to parcel. return new AppSearchResultParcelV2.Builder( WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, AppSearchResult.RESULT_OK) .setOpenBlobForReadResponse(OpenBlobForReadResponse) .build(); } @NonNull public AppSearchResult getResult() { return mResultCached; } /** @hide */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mWriteParcelMode); switch (mWriteParcelMode) { case WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB: byte[] bytes; // Create a parcel object to serialize results. 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. Parcel data = Parcel.obtain(); try { directlyWriteToParcel(this, data, flags); bytes = data.marshall(); } finally { data.recycle(); } ParcelableUtil.writeBlob(dest, bytes); break; case WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL: // It's important to add the PARCELABLE_WRITE_RETURN_VALUE flags to ensure // resources, such as ParcelFileDescriptor, are released on the sender's side. // Normally, PARCELABLE_WRITE_RETURN_VALUE is automatically added when a parcelable // object is directly returned in a binder call. // However, since AppSearch uses a callback mechanism and a void binder call // pattern, we need to manually add the PARCELABLE_WRITE_RETURN_VALUE flag when // parceling this object to invoke the callback. directlyWriteToParcel(this, dest, flags | PARCELABLE_WRITE_RETURN_VALUE); break; default: throw new UnsupportedOperationException( "Cannot write AppSearchResultParcelV2 to Parcel with unknown model: " + mWriteParcelMode); } } /** * Builder for {@link AppSearchResultParcelV2} objects. * * @param The type of the result objects for successful results. */ static final class Builder { @ParcelableUtil.WriteParcelMode private final int mWriteParcelMode; @AppSearchResult.ResultCode private final int mResultCode; @Nullable private String mErrorMessage; @Nullable private ParcelFileDescriptor mParcelFileDescriptor; @Nullable private OpenBlobForWriteResponse mOpenBlobForWriteResponse; @Nullable private CommitBlobResponse mCommitBlobResponse; @Nullable private OpenBlobForReadResponse mOpenBlobForReadResponse; @Nullable private RemoveBlobResponse mRemoveBlobResponse; /** Builds an {@link AppSearchResultParcelV2.Builder}. */ Builder(@ParcelableUtil.WriteParcelMode int writeParcelMode, int resultCode) { mWriteParcelMode = writeParcelMode; mResultCode = resultCode; } @CanIgnoreReturnValue Builder setErrorMessage(@Nullable String errorMessage) { mErrorMessage = errorMessage; return this; } @CanIgnoreReturnValue Builder setParcelFileDescriptor(ParcelFileDescriptor parcelFileDescriptor) { mParcelFileDescriptor = parcelFileDescriptor; return this; } @CanIgnoreReturnValue Builder setOpenBlobForWriteResponse( OpenBlobForWriteResponse openBlobForWriteResponse) { mOpenBlobForWriteResponse = openBlobForWriteResponse; return this; } @CanIgnoreReturnValue Builder setRemoveBlobResponse(RemoveBlobResponse removeBlobResponse) { mRemoveBlobResponse = removeBlobResponse; return this; } @CanIgnoreReturnValue Builder setCommitBlobResponse(CommitBlobResponse commitBlobResponse) { mCommitBlobResponse = commitBlobResponse; return this; } @CanIgnoreReturnValue Builder setOpenBlobForReadResponse( OpenBlobForReadResponse OpenBlobForReadResponse) { mOpenBlobForReadResponse = OpenBlobForReadResponse; return this; } /** * Builds an {@link AppSearchResultParcelV2} object from the contents of this {@link * AppSearchResultParcelV2.Builder}. */ @NonNull AppSearchResultParcelV2 build() { return new AppSearchResultParcelV2<>( mWriteParcelMode, mResultCode, mErrorMessage, mParcelFileDescriptor, mOpenBlobForWriteResponse, mCommitBlobResponse, mOpenBlobForReadResponse, mRemoveBlobResponse); } } }