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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.appsearch.AppSearchBatchResult; 22 import android.app.appsearch.AppSearchResult; 23 import android.app.appsearch.ParcelableUtil; 24 import android.app.appsearch.safeparcel.AbstractSafeParcelable; 25 import android.app.appsearch.safeparcel.GenericDocumentParcel; 26 import android.app.appsearch.safeparcel.SafeParcelable; 27 import android.os.Bundle; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 31 import java.util.Map; 32 import java.util.Objects; 33 34 /** 35 * Parcelable wrapper around {@link AppSearchBatchResult}. 36 * 37 * <p>{@link AppSearchBatchResult} can contain any type of key and value, including non-parcelable 38 * values. For the specific case of sending {@link AppSearchBatchResult} across Binder, this class 39 * wraps an {@link AppSearchBatchResult} that has String keys and Parcelable values. It provides 40 * parcelability of the whole structure. 41 * 42 * @param <ValueType> The type of result object for successful calls. Must be a parcelable type. 43 * @hide 44 */ 45 @SafeParcelable.Class(creator = "AppSearchBatchResultParcelCreator", creatorIsFinal = false) 46 public final class AppSearchBatchResultParcel<ValueType> extends AbstractSafeParcelable { 47 48 @NonNull 49 @SuppressWarnings("rawtypes") 50 public static final Parcelable.Creator<AppSearchBatchResultParcel> CREATOR = 51 new AppSearchBatchResultParcelCreator() { 52 @Override 53 public AppSearchBatchResultParcel createFromParcel(Parcel in) { 54 byte[] dataBlob = Objects.requireNonNull(ParcelableUtil.readBlob(in)); 55 Parcel unmarshallParcel = Parcel.obtain(); 56 try { 57 unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length); 58 unmarshallParcel.setDataPosition(0); 59 int size = unmarshallParcel.dataSize(); 60 Bundle inputBundle = new Bundle(); 61 while (unmarshallParcel.dataPosition() < size) { 62 String key = Objects.requireNonNull(unmarshallParcel.readString()); 63 AppSearchResultParcel appSearchResultParcel = 64 AppSearchResultParcel.directlyReadFromParcel(unmarshallParcel); 65 inputBundle.putParcelable(key, appSearchResultParcel); 66 } 67 return new AppSearchBatchResultParcel(inputBundle); 68 } finally { 69 unmarshallParcel.recycle(); 70 } 71 } 72 }; 73 74 // Map between String Key and AppSearchResultParcel Value. 75 @Field(id = 1) 76 @NonNull 77 final Bundle mAppSearchResultBundle; 78 79 @Nullable private AppSearchBatchResult<String, ValueType> mResultCached; 80 81 @Constructor AppSearchBatchResultParcel(@aramid = 1) Bundle appSearchResultBundle)82 AppSearchBatchResultParcel(@Param(id = 1) Bundle appSearchResultBundle) { 83 mAppSearchResultBundle = appSearchResultBundle; 84 } 85 86 /** 87 * Creates a new {@link AppSearchBatchResultParcel} from the given {@link GenericDocumentParcel} 88 * results. 89 */ 90 @SuppressWarnings("unchecked") 91 public static AppSearchBatchResultParcel<GenericDocumentParcel> fromStringToGenericDocumentParcel( @onNull AppSearchBatchResult<String, GenericDocumentParcel> result)92 fromStringToGenericDocumentParcel( 93 @NonNull AppSearchBatchResult<String, GenericDocumentParcel> result) { 94 Bundle appSearchResultBundle = new Bundle(); 95 for (Map.Entry<String, AppSearchResult<GenericDocumentParcel>> entry : 96 result.getAll().entrySet()) { 97 AppSearchResultParcel<GenericDocumentParcel> appSearchResultParcel; 98 // Create result from value in success case and errorMessage in 99 // failure case. 100 if (entry.getValue().isSuccess()) { 101 GenericDocumentParcel genericDocumentParcel = 102 Objects.requireNonNull(entry.getValue().getResultValue()); 103 appSearchResultParcel = 104 AppSearchResultParcel.fromGenericDocumentParcel(genericDocumentParcel); 105 } else { 106 appSearchResultParcel = AppSearchResultParcel.fromFailedResult(entry.getValue()); 107 } 108 appSearchResultBundle.putParcelable(entry.getKey(), appSearchResultParcel); 109 } 110 return new AppSearchBatchResultParcel<>(appSearchResultBundle); 111 } 112 113 /** Creates a new {@link AppSearchBatchResultParcel} from the given {@link Void} results. */ 114 @SuppressWarnings("unchecked") fromStringToVoid( @onNull AppSearchBatchResult<String, Void> result)115 public static AppSearchBatchResultParcel<Void> fromStringToVoid( 116 @NonNull AppSearchBatchResult<String, Void> result) { 117 Bundle appSearchResultBundle = new Bundle(); 118 for (Map.Entry<String, AppSearchResult<Void>> entry : result.getAll().entrySet()) { 119 AppSearchResultParcel<Void> appSearchResultParcel; 120 // Create result from value in success case and errorMessage in 121 // failure case. 122 if (entry.getValue().isSuccess()) { 123 appSearchResultParcel = AppSearchResultParcel.fromVoid(); 124 } else { 125 appSearchResultParcel = AppSearchResultParcel.fromFailedResult(entry.getValue()); 126 } 127 appSearchResultBundle.putParcelable(entry.getKey(), appSearchResultParcel); 128 } 129 return new AppSearchBatchResultParcel<>(appSearchResultBundle); 130 } 131 132 @NonNull 133 @SuppressWarnings("unchecked") getResult()134 public AppSearchBatchResult<String, ValueType> getResult() { 135 if (mResultCached == null) { 136 AppSearchBatchResult.Builder<String, ValueType> builder = 137 new AppSearchBatchResult.Builder<>(); 138 for (String key : mAppSearchResultBundle.keySet()) { 139 builder.setResult( 140 key, 141 mAppSearchResultBundle 142 .getParcelable(key, AppSearchResultParcel.class) 143 .getResult()); 144 } 145 mResultCached = builder.build(); 146 } 147 return mResultCached; 148 } 149 150 /** @hide */ 151 @Override 152 @SuppressWarnings("unchecked") writeToParcel(@onNull Parcel dest, int flags)153 public void writeToParcel(@NonNull Parcel dest, int flags) { 154 byte[] bytes; 155 // Create a parcel object to serialize results. So that we can use Parcel.writeBlob() to 156 // send data. WriteBlob() could take care of whether to pass data via binder directly or 157 // Android shared memory if the data is large. 158 Parcel data = Parcel.obtain(); 159 try { 160 for (String key : mAppSearchResultBundle.keySet()) { 161 data.writeString(key); 162 AppSearchResultParcel<ValueType> appSearchResultParcel = 163 mAppSearchResultBundle.getParcelable(key, AppSearchResultParcel.class); 164 AppSearchResultParcel.directlyWriteToParcel(appSearchResultParcel, data, flags); 165 } 166 bytes = data.marshall(); 167 } finally { 168 data.recycle(); 169 } 170 ParcelableUtil.writeBlob(dest, bytes); 171 } 172 } 173