• 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.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