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