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