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.AppSearchResult.RESULT_INTERNAL_ERROR; 20 import static android.app.appsearch.ParcelableUtil.WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL; 21 import static android.app.appsearch.ParcelableUtil.WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB; 22 23 import android.annotation.FlaggedApi; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.appsearch.AppSearchBatchResult; 27 import android.app.appsearch.AppSearchBlobHandle; 28 import android.app.appsearch.AppSearchResult; 29 import android.app.appsearch.ParcelableUtil; 30 import android.app.appsearch.safeparcel.AbstractSafeParcelable; 31 import android.app.appsearch.safeparcel.SafeParcelable; 32 import android.os.Bundle; 33 import android.os.Parcel; 34 import android.os.ParcelFileDescriptor; 35 import android.os.Parcelable; 36 37 import com.android.appsearch.flags.Flags; 38 39 import java.util.Map; 40 import java.util.Objects; 41 42 /** 43 * Parcelable wrapper around {@link AppSearchBatchResult}. 44 * 45 * <p>{@link AppSearchBatchResult} can contain any type of key and value, including non-parcelable 46 * values. For the specific case of sending {@link AppSearchBatchResult} across Binder, this class 47 * wraps an {@link AppSearchBatchResult} and provides parcelability of the whole structure. 48 * 49 * <p>Compare to deprecated {@link AppSearchBatchResultParcel}, this class could config how to write 50 * it to the parcel. Therefore binder objects and {@link ParcelFileDescriptor} is supported in this 51 * class. This class could also support general type as KeyType. 52 * 53 * @see ParcelableUtil#WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB 54 * @see ParcelableUtil#WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL 55 * @param <KeyType> The type of the keys for which the results will be reported. We are passing the 56 * class name of the KeyType to parcelable. Do not rename the class of KeyType, since that may 57 * cause compatibility issue for GmsCore. 58 * @param <ValueType> The type of result object for successful calls. Must be a parcelable type. 59 * @hide 60 */ 61 @SafeParcelable.Class(creator = "AppSearchBatchResultParcelV2Creator", creatorIsFinal = false) 62 public final class AppSearchBatchResultParcelV2<KeyType, ValueType> extends AbstractSafeParcelable { 63 private static final String TAG = "AppSearchBatchResultPar"; 64 65 @NonNull 66 // Provide ClassLoader when read from bundle in getResult() method 67 @SuppressWarnings("rawtypes") 68 public static final Parcelable.Creator<AppSearchBatchResultParcelV2> CREATOR = 69 new AppSearchBatchResultParcelV2Creator() { 70 @Override 71 public AppSearchBatchResultParcelV2 createFromParcel(Parcel in) { 72 int writeParcelModel = in.readInt(); 73 switch (writeParcelModel) { 74 case WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB: 75 byte[] dataBlob = Objects.requireNonNull(ParcelableUtil.readBlob(in)); 76 Parcel unmarshallParcel = Parcel.obtain(); 77 try { 78 unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length); 79 unmarshallParcel.setDataPosition(0); 80 return directlyReadFromParcel(unmarshallParcel); 81 } finally { 82 unmarshallParcel.recycle(); 83 } 84 case WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL: 85 return directlyReadFromParcel(in); 86 default: 87 throw new UnsupportedOperationException( 88 "Cannot write AppSearchBatchResultParcelV2 to Parcel with " 89 + "unknown model: " 90 + writeParcelModel); 91 } 92 } 93 }; 94 95 /** The Creator used to directly write to parcel with calling {@link Parcel#writeBlob}. */ 96 @NonNull 97 private static final Parcelable.Creator<AppSearchBatchResultParcelV2> CREATOR_WITHOUT_BLOB = 98 new AppSearchBatchResultParcelV2Creator(); 99 directlyReadFromParcel(@onNull Parcel data)100 static AppSearchBatchResultParcelV2<?, ?> directlyReadFromParcel(@NonNull Parcel data) { 101 return CREATOR_WITHOUT_BLOB.createFromParcel(data); 102 } 103 directlyWriteToParcel( @onNull AppSearchBatchResultParcelV2<?, ?> result, @NonNull Parcel data, int flags)104 static void directlyWriteToParcel( 105 @NonNull AppSearchBatchResultParcelV2<?, ?> result, @NonNull Parcel data, int flags) { 106 AppSearchBatchResultParcelV2Creator.writeToParcel(result, data, flags); 107 } 108 109 /** 110 * The flags indicate how we write this object to parcel and read it. 111 * 112 * @see ParcelableUtil#WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB 113 * @see ParcelableUtil#WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL 114 */ 115 @Field(id = 1) 116 @ParcelableUtil.WriteParcelMode 117 int mWriteParcelModel; 118 119 // No longer used, we shouldn't use class name to generate clazz file since proguard will 120 // mutate the class name. 121 @Field(id = 2) 122 @NonNull 123 final String mKeyClassName; 124 125 // Map stores keys of AppSearchBatchResult. The key will be an integer of a consecutive 126 // increasing sequence. Associated with mAppSearchResultValueBundle. 127 @Field(id = 3) 128 @NonNull 129 final Bundle mKeyBundle; 130 131 // Map stores keys of AppSearchBatchResult. The key will be an integer of a consecutive 132 // increasing sequence. Associated with mKeyBundle. 133 @Field(id = 4) 134 @NonNull 135 final Bundle mAppSearchResultValueBundle; 136 137 @Nullable private AppSearchBatchResult<KeyType, ValueType> mResultCached; 138 139 @Constructor AppSearchBatchResultParcelV2( @aramid = 1) @arcelableUtil.WriteParcelMode int writeParcelModel, @Param(id = 2) String keyClassName, @Param(id = 3) Bundle keyBundle, @Param(id = 4) Bundle appSearchResultValueBundle)140 AppSearchBatchResultParcelV2( 141 @Param(id = 1) @ParcelableUtil.WriteParcelMode int writeParcelModel, 142 @Param(id = 2) String keyClassName, 143 @Param(id = 3) Bundle keyBundle, 144 @Param(id = 4) Bundle appSearchResultValueBundle) { 145 mWriteParcelModel = writeParcelModel; 146 mKeyClassName = keyClassName; 147 mKeyBundle = keyBundle; 148 mAppSearchResultValueBundle = appSearchResultValueBundle; 149 150 // We need to set the bundle's class loader otherwise it may return null in getParcelable. 151 // Normally all AppSearch's classes should be under the same classLoader, using 152 // AppSearchResultParcelV2.class.getClassLoader() here. 153 ClassLoader classLoader = AppSearchResultParcelV2.class.getClassLoader(); 154 mKeyBundle.setClassLoader(classLoader); 155 mAppSearchResultValueBundle.setClassLoader(classLoader); 156 } 157 158 /** 159 * Creates a new {@link AppSearchBatchResultParcel} from the given {@link AppSearchBatchResult} 160 * results which has {@link AppSearchBlobHandle} as keys and {@link ParcelFileDescriptor} as 161 * values. 162 */ 163 @SuppressWarnings("unchecked") 164 @NonNull 165 @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE) 166 public static AppSearchBatchResultParcelV2<AppSearchBlobHandle, ParcelFileDescriptor> fromBlobHandleToPfd( @onNull AppSearchBatchResult<AppSearchBlobHandle, ParcelFileDescriptor> result)167 fromBlobHandleToPfd( 168 @NonNull 169 AppSearchBatchResult<AppSearchBlobHandle, ParcelFileDescriptor> 170 result) { 171 Bundle keyAppSearchResultBundle = new Bundle(); 172 Bundle valueAppSearchResultBundle = new Bundle(); 173 int i = 0; 174 for (Map.Entry<AppSearchBlobHandle, AppSearchResult<ParcelFileDescriptor>> entry : 175 result.getAll().entrySet()) { 176 AppSearchResultParcelV2<ParcelFileDescriptor> valueAppSearchBinderResultParcel; 177 // Create result from value in success case and errorMessage in failure case. 178 if (entry.getValue().isSuccess()) { 179 valueAppSearchBinderResultParcel = 180 AppSearchResultParcelV2.fromParcelFileDescriptor( 181 entry.getValue().getResultValue()); 182 } else { 183 valueAppSearchBinderResultParcel = 184 AppSearchResultParcelV2.fromFailedResult(entry.getValue()); 185 } 186 keyAppSearchResultBundle.putParcelable(String.valueOf(i), entry.getKey()); 187 valueAppSearchResultBundle.putParcelable( 188 String.valueOf(i), valueAppSearchBinderResultParcel); 189 ++i; 190 } 191 // We cannot marshall PFD!! We have to directly write this object to parcel. 192 return new AppSearchBatchResultParcelV2<>( 193 WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL, 194 AppSearchBlobHandle.class.getName(), 195 keyAppSearchResultBundle, 196 valueAppSearchResultBundle); 197 } 198 199 /** 200 * Creates a new {@link AppSearchBatchResultParcel} from the given {@link AppSearchBatchResult} 201 * results which has {@link AppSearchBlobHandle} as keys and {@code Void} as values. 202 */ 203 @NonNull 204 @FlaggedApi(Flags.FLAG_ENABLE_BLOB_STORE) fromBlobHandleToVoid( @onNull AppSearchBatchResult<AppSearchBlobHandle, Void> result)205 public static AppSearchBatchResultParcelV2<AppSearchBlobHandle, Void> fromBlobHandleToVoid( 206 @NonNull AppSearchBatchResult<AppSearchBlobHandle, Void> result) { 207 Bundle keyAppSearchResultBundle = new Bundle(); 208 Bundle valueAppSearchResultBundle = new Bundle(); 209 int i = 0; 210 for (Map.Entry<AppSearchBlobHandle, AppSearchResult<Void>> entry : 211 result.getAll().entrySet()) { 212 AppSearchResultParcelV2<ParcelFileDescriptor> valueAppSearchResultParcel; 213 // Create result from value in success case and errorMessage in failure case. 214 if (entry.getValue().isSuccess()) { 215 valueAppSearchResultParcel = AppSearchResultParcelV2.fromVoid(); 216 } else { 217 valueAppSearchResultParcel = 218 AppSearchResultParcelV2.fromFailedResult(entry.getValue()); 219 } 220 keyAppSearchResultBundle.putParcelable(String.valueOf(i), entry.getKey()); 221 valueAppSearchResultBundle.putParcelable(String.valueOf(i), valueAppSearchResultParcel); 222 ++i; 223 } 224 return new AppSearchBatchResultParcelV2<>( 225 WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB, 226 AppSearchBlobHandle.class.getName(), 227 keyAppSearchResultBundle, 228 valueAppSearchResultBundle); 229 } 230 231 /** Gets the {@link AppSearchBatchResult} out of this {@link AppSearchBatchResultParcelV2}. */ 232 @NonNull 233 @SuppressWarnings({"unchecked", "deprecation"}) getResult()234 public AppSearchBatchResult<KeyType, ValueType> getResult() { 235 if (mResultCached == null) { 236 AppSearchBatchResult.Builder<KeyType, ValueType> builder = 237 new AppSearchBatchResult.Builder<>(); 238 239 for (String key : mKeyBundle.keySet()) { 240 KeyType keyType = (KeyType) mKeyBundle.getParcelable(key); 241 AppSearchResultParcelV2<ValueType> valueTypeResult = 242 (AppSearchResultParcelV2<ValueType>) 243 mAppSearchResultValueBundle.getParcelable(key); 244 if (keyType == null) { 245 // keyType is null means the type of key doesn't match keyClazz, which 246 // is impossible. 247 throw new IllegalArgumentException( 248 "AppSearchResultParcelV2's key type doesn't match."); 249 } else if (valueTypeResult == null) { 250 // valueTypeResult is null means the type of value isn't 251 // AppSearchResultParcelV2, which is impossible. 252 builder.setResult( 253 keyType, 254 AppSearchResult.newFailedResult( 255 RESULT_INTERNAL_ERROR, 256 "Cannot read value parcelable from bundle.")); 257 } else { 258 builder.setResult(keyType, valueTypeResult.getResult()); 259 } 260 } 261 mResultCached = builder.build(); 262 } 263 return mResultCached; 264 } 265 266 /** @hide */ 267 @Override 268 @SuppressWarnings("unchecked") writeToParcel(@onNull Parcel dest, int flags)269 public void writeToParcel(@NonNull Parcel dest, int flags) { 270 dest.writeInt(mWriteParcelModel); 271 switch (mWriteParcelModel) { 272 case WRITE_PARCEL_MODE_MARSHALL_WRITE_IN_BLOB: 273 byte[] bytes; 274 // Create a parcel object to serialize results. So that we can use 275 // Parcel.writeBlob() to 276 // send data. WriteBlob() could take care of whether to pass data via binder 277 // directly or 278 // Android shared memory if the data is large. 279 Parcel data = Parcel.obtain(); 280 try { 281 directlyWriteToParcel(this, data, flags); 282 bytes = data.marshall(); 283 } finally { 284 data.recycle(); 285 } 286 ParcelableUtil.writeBlob(dest, bytes); 287 break; 288 case WRITE_PARCEL_MODE_DIRECTLY_WRITE_TO_PARCEL: 289 directlyWriteToParcel(this, dest, flags); 290 break; 291 default: 292 throw new UnsupportedOperationException( 293 "Cannot read AppSearchBatchResultParcelV2 from Parcel with " 294 + "unknown model: " 295 + mWriteParcelModel); 296 } 297 } 298 } 299