• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app.appsearch.aidl;
18 
19 import static android.app.appsearch.ParcelableUtil.WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL;
20 import static android.app.appsearch.ParcelableUtil.WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB;
21 import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.appsearch.AppSearchResult;
26 import android.app.appsearch.CommitBlobResponse;
27 import android.app.appsearch.OpenBlobForReadResponse;
28 import android.app.appsearch.OpenBlobForWriteResponse;
29 import android.app.appsearch.ParcelableUtil;
30 import android.app.appsearch.RemoveBlobResponse;
31 import android.app.appsearch.annotation.CanIgnoreReturnValue;
32 import android.app.appsearch.safeparcel.AbstractSafeParcelable;
33 import android.app.appsearch.safeparcel.SafeParcelable;
34 import android.os.Parcel;
35 import android.os.ParcelFileDescriptor;
36 import android.os.Parcelable;
37 
38 import java.util.Objects;
39 
40 /**
41  * Parcelable wrapper around {@link AppSearchResult}.
42  *
43  * <p>{@link AppSearchResult} can contain any value, including non-parcelable values. For the
44  * specific case of sending {@link AppSearchResult} across Binder, this class wraps an {@link
45  * AppSearchResult} that contains a parcelable type and provides parcelability of the whole
46  * structure.
47  *
48  * <p>Compare to deprecated {@link AppSearchResultParcel}, this class could config how to write it
49  * to the parcel. Therefore binder objects and {@link ParcelFileDescriptor} is supported in this
50  * class.
51  *
52  * @see ParcelableUtil#WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB
53  * @see ParcelableUtil#WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL
54  * @param <ValueType> The type of result object for successful calls. Must be a parcelable type.
55  * @hide
56  */
57 @SafeParcelable.Class(creator = "AppSearchResultParcelV2Creator", creatorIsFinal = false)
58 public final class AppSearchResultParcelV2<ValueType> extends AbstractSafeParcelable {
59     private static final String TAG = "AppSearchResultParcelV2";
60 
61     @NonNull
62     @SuppressWarnings("rawtypes")
63     public static final Parcelable.Creator<AppSearchResultParcelV2> CREATOR =
64             new AppSearchResultParcelV2Creator() {
65                 @Override
66                 public AppSearchResultParcelV2 createFromParcel(Parcel in) {
67                     int writeParcelModel = in.readInt();
68                     switch (writeParcelModel) {
69                         case WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB:
70                             byte[] dataBlob = Objects.requireNonNull(ParcelableUtil.readBlob(in));
71                             Parcel unmarshallParcel = Parcel.obtain();
72                             try {
73                                 unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length);
74                                 unmarshallParcel.setDataPosition(0);
75                                 return directlyReadFromParcel(unmarshallParcel);
76                             } finally {
77                                 unmarshallParcel.recycle();
78                             }
79                         case WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL:
80                             return directlyReadFromParcel(in);
81                         default:
82                             throw new UnsupportedOperationException(
83                                     "Cannot read AppSearchResultParcelV2 from Parcel with "
84                                             + "unknown model: "
85                                             + writeParcelModel);
86                     }
87                 }
88             };
89 
90     @NonNull
91     private static final Parcelable.Creator<AppSearchResultParcelV2> CREATOR_WITHOUT_BLOB =
92             new AppSearchResultParcelV2Creator();
93 
directlyReadFromParcel(@onNull Parcel data)94     static AppSearchResultParcelV2<?> directlyReadFromParcel(@NonNull Parcel data) {
95         return CREATOR_WITHOUT_BLOB.createFromParcel(data);
96     }
97 
directlyWriteToParcel( @onNull AppSearchResultParcelV2<?> result, @NonNull Parcel data, int flags)98     static void directlyWriteToParcel(
99             @NonNull AppSearchResultParcelV2<?> result, @NonNull Parcel data, int flags) {
100         AppSearchResultParcelV2Creator.writeToParcel(result, data, flags);
101     }
102 
103     /**
104      * The flags indicate how we write this object to parcel and read it.
105      *
106      * @see ParcelableUtil#WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB
107      * @see ParcelableUtil#WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL
108      */
109     @Field(id = 1)
110     @ParcelableUtil.WriteParcelMode
111     int mWriteParcelMode;
112 
113     @Field(id = 2)
114     @AppSearchResult.ResultCode
115     int mResultCode;
116 
117     @Field(id = 3)
118     @Nullable
119     String mErrorMessage;
120 
121     @Field(id = 4)
122     @Nullable
123     ParcelFileDescriptor mParcelFileDescriptor;
124 
125     @Field(id = 5)
126     @Nullable
127     OpenBlobForWriteResponse mOpenBlobForWriteResponse;
128 
129     @Field(id = 6)
130     @Nullable
131     CommitBlobResponse mCommitBlobResponse;
132 
133     @Field(id = 7)
134     @Nullable
135     OpenBlobForReadResponse mOpenBlobForReadResponse;
136 
137     @Field(id = 8)
138     @Nullable
139     RemoveBlobResponse mRemoveBlobResponse;
140 
141     @NonNull AppSearchResult<ValueType> mResultCached;
142 
143     /**
144      * Creates an AppSearchResultParcelV2 for given value type.
145      *
146      * @param resultCode A {@link AppSearchResult} result code for {@link IAppSearchManager} API
147      *     response.
148      * @param errorMessage An error message in case of a failed response.
149      */
150     @Constructor
AppSearchResultParcelV2( @aramid = 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)151     AppSearchResultParcelV2(
152             @Param(id = 1) int writeParcelMode,
153             @Param(id = 2) @AppSearchResult.ResultCode int resultCode,
154             @Param(id = 3) @Nullable String errorMessage,
155             @Param(id = 4) @Nullable ParcelFileDescriptor parcelFileDescriptor,
156             @Param(id = 5) @Nullable OpenBlobForWriteResponse openBlobForWriteResponse,
157             @Param(id = 6) @Nullable CommitBlobResponse commitBlobResponse,
158             @Param(id = 7) @Nullable OpenBlobForReadResponse openBlobForReadResponse,
159             @Param(id = 8) @Nullable RemoveBlobResponse removeBlobResponse) {
160         mWriteParcelMode = writeParcelMode;
161         mResultCode = resultCode;
162         mErrorMessage = errorMessage;
163         if (resultCode == AppSearchResult.RESULT_OK) {
164             mParcelFileDescriptor = parcelFileDescriptor;
165             mOpenBlobForWriteResponse = openBlobForWriteResponse;
166             mCommitBlobResponse = commitBlobResponse;
167             mOpenBlobForReadResponse = openBlobForReadResponse;
168             mRemoveBlobResponse = removeBlobResponse;
169             if (mParcelFileDescriptor != null) {
170                 mResultCached =
171                         (AppSearchResult<ValueType>)
172                                 AppSearchResult.newSuccessfulResult(mParcelFileDescriptor);
173             } else if (mOpenBlobForWriteResponse != null) {
174                 mResultCached =
175                         (AppSearchResult<ValueType>)
176                                 AppSearchResult.newSuccessfulResult(mOpenBlobForWriteResponse);
177             } else if (mCommitBlobResponse != null) {
178                 mResultCached =
179                         (AppSearchResult<ValueType>)
180                                 AppSearchResult.newSuccessfulResult(mCommitBlobResponse);
181             } else if (mOpenBlobForReadResponse != null) {
182                 mResultCached =
183                         (AppSearchResult<ValueType>)
184                                 AppSearchResult.newSuccessfulResult(mOpenBlobForReadResponse);
185             } else if (mRemoveBlobResponse != null) {
186                 mResultCached =
187                         (AppSearchResult<ValueType>)
188                                 AppSearchResult.newSuccessfulResult(mRemoveBlobResponse);
189             } else {
190                 // Default case where code is OK and value is null.
191                 mResultCached = AppSearchResult.newSuccessfulResult(null);
192             }
193         } else {
194             mResultCached = AppSearchResult.newFailedResult(mResultCode, mErrorMessage);
195         }
196     }
197 
198     /**
199      * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful Void
200      * response.
201      */
fromVoid()202     public static AppSearchResultParcelV2 fromVoid() {
203         // We can marshall a void results, but since it is always a small object, we can directly
204         // write it to parcel.
205         return new AppSearchResultParcelV2.Builder<>(
206                         WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, AppSearchResult.RESULT_OK)
207                 .build();
208     }
209 
210     /** Creates a new failed {@link AppSearchResultParcelV2} from result code and error message. */
211     @SuppressWarnings({"unchecked", "rawtypes"})
fromFailedResult(AppSearchResult failedResult)212     public static AppSearchResultParcelV2 fromFailedResult(AppSearchResult failedResult) {
213         if (failedResult.isSuccess()) {
214             throw new IllegalStateException(
215                     "Creating a failed AppSearchResultParcelV2 from a " + "successful response");
216         }
217         // We can marshall a failed results, but since it is always a small object, we can directly
218         // write it to parcel.
219         return new AppSearchResultParcelV2.Builder<>(
220                         WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, failedResult.getResultCode())
221                 .setErrorMessage(failedResult.getErrorMessage())
222                 .build();
223     }
224 
225     /**
226      * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful
227      * {@link ParcelFileDescriptor}.
228      */
fromParcelFileDescriptor( ParcelFileDescriptor parcelFileDescriptor)229     public static AppSearchResultParcelV2<ParcelFileDescriptor> fromParcelFileDescriptor(
230             ParcelFileDescriptor parcelFileDescriptor) {
231         // We CANNOT marshall a FD, we have to directly write it to parcel.
232         return new AppSearchResultParcelV2.Builder<ParcelFileDescriptor>(
233                         WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, AppSearchResult.RESULT_OK)
234                 .setParcelFileDescriptor(parcelFileDescriptor)
235                 .build();
236     }
237 
238     /**
239      * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful
240      * {@link OpenBlobForWriteResponse}.
241      */
fromOpenBlobForWriteResponse( OpenBlobForWriteResponse openBlobForWriteResponse)242     public static AppSearchResultParcelV2<OpenBlobForWriteResponse> fromOpenBlobForWriteResponse(
243             OpenBlobForWriteResponse openBlobForWriteResponse) {
244         // We CANNOT marshall OpenBlobForWriteResponse, since it contains FD, we have to directly
245         // write it to parcel.
246         return new AppSearchResultParcelV2.Builder<OpenBlobForWriteResponse>(
247                         WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, AppSearchResult.RESULT_OK)
248                 .setOpenBlobForWriteResponse(openBlobForWriteResponse)
249                 .build();
250     }
251 
252     /**
253      * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful
254      * {@link RemoveBlobResponse}.
255      */
fromRemoveBlobResponseParcel( RemoveBlobResponse removeBlobResponse)256     public static AppSearchResultParcelV2<RemoveBlobResponse> fromRemoveBlobResponseParcel(
257             RemoveBlobResponse removeBlobResponse) {
258         return new AppSearchResultParcelV2.Builder<RemoveBlobResponse>(
259                         WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB, AppSearchResult.RESULT_OK)
260                 .setRemoveBlobResponse(removeBlobResponse)
261                 .build();
262     }
263 
264     /**
265      * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful
266      * {@link CommitBlobResponse}.
267      */
fromCommitBlobResponseParcel( CommitBlobResponse commitBlobResponse)268     public static AppSearchResultParcelV2<CommitBlobResponse> fromCommitBlobResponseParcel(
269             CommitBlobResponse commitBlobResponse) {
270         return new AppSearchResultParcelV2.Builder<CommitBlobResponse>(
271                         WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB, AppSearchResult.RESULT_OK)
272                 .setCommitBlobResponse(commitBlobResponse)
273                 .build();
274     }
275 
276     /**
277      * Creates a new {@link AppSearchResultParcelV2} from the given result in case a successful
278      * {@link OpenBlobForReadResponse}.
279      */
fromOpenBlobForReadResponse( OpenBlobForReadResponse OpenBlobForReadResponse)280     public static AppSearchResultParcelV2<OpenBlobForReadResponse> fromOpenBlobForReadResponse(
281             OpenBlobForReadResponse OpenBlobForReadResponse) {
282         // We CANNOT marshall OpenBlobForReadResponse, since it contains FD, we have to directly
283         // write it to parcel.
284         return new AppSearchResultParcelV2.Builder<OpenBlobForReadResponse>(
285                         WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, AppSearchResult.RESULT_OK)
286                 .setOpenBlobForReadResponse(OpenBlobForReadResponse)
287                 .build();
288     }
289 
290     @NonNull
getResult()291     public AppSearchResult<ValueType> getResult() {
292         return mResultCached;
293     }
294 
295     /** @hide */
296     @Override
writeToParcel(@onNull Parcel dest, int flags)297     public void writeToParcel(@NonNull Parcel dest, int flags) {
298         dest.writeInt(mWriteParcelMode);
299         switch (mWriteParcelMode) {
300             case WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB:
301                 byte[] bytes;
302                 // Create a parcel object to serialize results. So that we can use
303                 // Parcel.writeBlob() to
304                 // send data. WriteBlob() could take care of whether to pass data via binder
305                 // directly or
306                 // Android shared memory if the data is large.
307                 Parcel data = Parcel.obtain();
308                 try {
309                     directlyWriteToParcel(this, data, flags);
310                     bytes = data.marshall();
311                 } finally {
312                     data.recycle();
313                 }
314                 ParcelableUtil.writeBlob(dest, bytes);
315                 break;
316             case WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL:
317                 // It's important to add the PARCELABLE_WRITE_RETURN_VALUE flags to ensure
318                 // resources, such as ParcelFileDescriptor, are released on the sender's side.
319                 // Normally, PARCELABLE_WRITE_RETURN_VALUE is automatically added when a parcelable
320                 // object is directly returned in a binder call.
321                 // However, since AppSearch uses a callback mechanism and a void binder call
322                 // pattern, we need to manually add the PARCELABLE_WRITE_RETURN_VALUE flag when
323                 // parceling this object to invoke the callback.
324                 directlyWriteToParcel(this, dest, flags | PARCELABLE_WRITE_RETURN_VALUE);
325                 break;
326             default:
327                 throw new UnsupportedOperationException(
328                         "Cannot write AppSearchResultParcelV2 to Parcel with unknown model: "
329                                 + mWriteParcelMode);
330         }
331     }
332 
333     /**
334      * Builder for {@link AppSearchResultParcelV2} objects.
335      *
336      * @param <ValueType> The type of the result objects for successful results.
337      */
338     static final class Builder<ValueType> {
339 
340         @ParcelableUtil.WriteParcelMode private final int mWriteParcelMode;
341         @AppSearchResult.ResultCode private final int mResultCode;
342         @Nullable private String mErrorMessage;
343         @Nullable private ParcelFileDescriptor mParcelFileDescriptor;
344         @Nullable private OpenBlobForWriteResponse mOpenBlobForWriteResponse;
345         @Nullable private CommitBlobResponse mCommitBlobResponse;
346         @Nullable private OpenBlobForReadResponse mOpenBlobForReadResponse;
347         @Nullable private RemoveBlobResponse mRemoveBlobResponse;
348 
349         /** Builds an {@link AppSearchResultParcelV2.Builder}. */
Builder(@arcelableUtil.WriteParcelMode int writeParcelMode, int resultCode)350         Builder(@ParcelableUtil.WriteParcelMode int writeParcelMode, int resultCode) {
351             mWriteParcelMode = writeParcelMode;
352             mResultCode = resultCode;
353         }
354 
355         @CanIgnoreReturnValue
setErrorMessage(@ullable String errorMessage)356         Builder<ValueType> setErrorMessage(@Nullable String errorMessage) {
357             mErrorMessage = errorMessage;
358             return this;
359         }
360 
361         @CanIgnoreReturnValue
setParcelFileDescriptor(ParcelFileDescriptor parcelFileDescriptor)362         Builder<ValueType> setParcelFileDescriptor(ParcelFileDescriptor parcelFileDescriptor) {
363             mParcelFileDescriptor = parcelFileDescriptor;
364             return this;
365         }
366 
367         @CanIgnoreReturnValue
setOpenBlobForWriteResponse( OpenBlobForWriteResponse openBlobForWriteResponse)368         Builder<ValueType> setOpenBlobForWriteResponse(
369                 OpenBlobForWriteResponse openBlobForWriteResponse) {
370             mOpenBlobForWriteResponse = openBlobForWriteResponse;
371             return this;
372         }
373 
374         @CanIgnoreReturnValue
setRemoveBlobResponse(RemoveBlobResponse removeBlobResponse)375         Builder<ValueType> setRemoveBlobResponse(RemoveBlobResponse removeBlobResponse) {
376             mRemoveBlobResponse = removeBlobResponse;
377             return this;
378         }
379 
380         @CanIgnoreReturnValue
setCommitBlobResponse(CommitBlobResponse commitBlobResponse)381         Builder<ValueType> setCommitBlobResponse(CommitBlobResponse commitBlobResponse) {
382             mCommitBlobResponse = commitBlobResponse;
383             return this;
384         }
385 
386         @CanIgnoreReturnValue
setOpenBlobForReadResponse( OpenBlobForReadResponse OpenBlobForReadResponse)387         Builder<ValueType> setOpenBlobForReadResponse(
388                 OpenBlobForReadResponse OpenBlobForReadResponse) {
389             mOpenBlobForReadResponse = OpenBlobForReadResponse;
390             return this;
391         }
392 
393         /**
394          * Builds an {@link AppSearchResultParcelV2} object from the contents of this {@link
395          * AppSearchResultParcelV2.Builder}.
396          */
397         @NonNull
build()398         AppSearchResultParcelV2<ValueType> build() {
399             return new AppSearchResultParcelV2<>(
400                     mWriteParcelMode,
401                     mResultCode,
402                     mErrorMessage,
403                     mParcelFileDescriptor,
404                     mOpenBlobForWriteResponse,
405                     mCommitBlobResponse,
406                     mOpenBlobForReadResponse,
407                     mRemoveBlobResponse);
408         }
409     }
410 }
411