blob: c28d2bbe30ea63fff65e18fad411f44a45a694fd [file] [log] [blame]
Felipe Leme979013d2017-06-22 10:59:23 -07001/*
2 * Copyright (C) 2017 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
17package android.service.autofill;
18
19import static android.view.autofill.Helper.sDebug;
20
21import android.annotation.NonNull;
Felipe Leme63c601a2017-09-27 17:40:30 -070022import android.annotation.Nullable;
Felipe Leme37f83722018-08-16 13:12:03 -070023import android.annotation.TestApi;
Felipe Lemec24a56a2017-08-03 14:27:57 -070024import android.app.Activity;
25import android.app.PendingIntent;
Felipe Leme979013d2017-06-22 10:59:23 -070026import android.os.Parcel;
27import android.os.Parcelable;
Felipe Lemededf8f12017-08-10 13:46:14 -070028import android.util.Pair;
Felipe Leme37f83722018-08-16 13:12:03 -070029import android.util.SparseArray;
Felipe Leme979013d2017-06-22 10:59:23 -070030import android.widget.RemoteViews;
31
32import com.android.internal.util.Preconditions;
33
Felipe Lemededf8f12017-08-10 13:46:14 -070034import java.util.ArrayList;
35
Felipe Leme979013d2017-06-22 10:59:23 -070036/**
Felipe Leme2c888422017-10-26 12:46:35 -070037 * Defines a custom description for the autofill save UI.
Felipe Leme979013d2017-06-22 10:59:23 -070038 *
39 * <p>This is useful when the autofill service needs to show a detailed view of what would be saved;
40 * for example, when the screen contains a credit card, it could display a logo of the credit card
Felipe Lemec7cea5b2017-08-02 09:50:15 -070041 * bank, the last four digits of the credit card number, and its expiration number.
Felipe Leme979013d2017-06-22 10:59:23 -070042 *
43 * <p>A custom description is made of 2 parts:
44 * <ul>
45 * <li>A {@link RemoteViews presentation template} containing children views.
46 * <li>{@link Transformation Transformations} to populate the children views.
47 * </ul>
48 *
49 * <p>For the credit card example mentioned above, the (simplified) template would be:
50 *
51 * <pre class="prettyprint">
52 * &lt;LinearLayout&gt;
53 * &lt;ImageView android:id="@+id/templateccLogo"/&gt;
54 * &lt;TextView android:id="@+id/templateCcNumber"/&gt;
55 * &lt;TextView android:id="@+id/templateExpDate"/&gt;
56 * &lt;/LinearLayout&gt;
57 * </pre>
58 *
59 * <p>Which in code translates to:
60 *
61 * <pre class="prettyprint">
62 * CustomDescription.Builder buider = new Builder(new RemoteViews(pgkName, R.layout.cc_template);
63 * </pre>
64 *
65 * <p>Then the value of each of the 3 children would be changed at runtime based on the the value of
66 * the screen fields and the {@link Transformation Transformations}:
67 *
68 * <pre class="prettyprint">
69 * // Image child - different logo for each bank, based on credit card prefix
70 * builder.addChild(R.id.templateccLogo,
71 * new ImageTransformation.Builder(ccNumberId)
Felipe Leme63c601a2017-09-27 17:40:30 -070072 * .addOption(Pattern.compile("^4815.*$"), R.drawable.ic_credit_card_logo1)
73 * .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2)
74 * .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3)
Felipe Lemededf8f12017-08-10 13:46:14 -070075 * .build();
Felipe Leme979013d2017-06-22 10:59:23 -070076 * // Masked credit card number (as .....LAST_4_DIGITS)
Felipe Lemededf8f12017-08-10 13:46:14 -070077 * builder.addChild(R.id.templateCcNumber, new CharSequenceTransformation
Felipe Leme63c601a2017-09-27 17:40:30 -070078 * .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
Felipe Lemededf8f12017-08-10 13:46:14 -070079 * .build();
Felipe Leme979013d2017-06-22 10:59:23 -070080 * // Expiration date as MM / YYYY:
Felipe Lemededf8f12017-08-10 13:46:14 -070081 * builder.addChild(R.id.templateExpDate, new CharSequenceTransformation
Felipe Leme63c601a2017-09-27 17:40:30 -070082 * .Builder(ccExpMonthId, Pattern.compile("^(\\d\\d)$"), "Exp: $1")
83 * .addField(ccExpYearId, Pattern.compile("^(\\d\\d)$"), "/$1")
Felipe Lemededf8f12017-08-10 13:46:14 -070084 * .build();
Felipe Leme979013d2017-06-22 10:59:23 -070085 * </pre>
86 *
87 * <p>See {@link ImageTransformation}, {@link CharSequenceTransformation} for more info about these
88 * transformations.
89 */
Felipe Leme979013d2017-06-22 10:59:23 -070090public final class CustomDescription implements Parcelable {
91
Felipe Leme979013d2017-06-22 10:59:23 -070092 private final RemoteViews mPresentation;
Felipe Lemededf8f12017-08-10 13:46:14 -070093 private final ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
Felipe Leme63c601a2017-09-27 17:40:30 -070094 private final ArrayList<Pair<InternalValidator, BatchUpdates>> mUpdates;
Felipe Leme37f83722018-08-16 13:12:03 -070095 private final SparseArray<InternalOnClickAction> mActions;
Felipe Leme979013d2017-06-22 10:59:23 -070096
97 private CustomDescription(Builder builder) {
98 mPresentation = builder.mPresentation;
99 mTransformations = builder.mTransformations;
Felipe Leme63c601a2017-09-27 17:40:30 -0700100 mUpdates = builder.mUpdates;
Felipe Leme37f83722018-08-16 13:12:03 -0700101 mActions = builder.mActions;
Felipe Leme979013d2017-06-22 10:59:23 -0700102 }
103
104 /** @hide */
Felipe Leme63c601a2017-09-27 17:40:30 -0700105 @Nullable
106 public RemoteViews getPresentation() {
Felipe Leme979013d2017-06-22 10:59:23 -0700107 return mPresentation;
108 }
109
Felipe Leme63c601a2017-09-27 17:40:30 -0700110 /** @hide */
111 @Nullable
112 public ArrayList<Pair<Integer, InternalTransformation>> getTransformations() {
113 return mTransformations;
114 }
115
116 /** @hide */
117 @Nullable
118 public ArrayList<Pair<InternalValidator, BatchUpdates>> getUpdates() {
119 return mUpdates;
120 }
121
Felipe Leme37f83722018-08-16 13:12:03 -0700122 /** @hide */
123 @Nullable
124 @TestApi
125 public SparseArray<InternalOnClickAction> getActions() {
126 return mActions;
127 }
128
Felipe Leme979013d2017-06-22 10:59:23 -0700129 /**
130 * Builder for {@link CustomDescription} objects.
131 */
132 public static class Builder {
133 private final RemoteViews mPresentation;
134
Felipe Leme63c601a2017-09-27 17:40:30 -0700135 private boolean mDestroyed;
Felipe Lemededf8f12017-08-10 13:46:14 -0700136 private ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
Felipe Leme63c601a2017-09-27 17:40:30 -0700137 private ArrayList<Pair<InternalValidator, BatchUpdates>> mUpdates;
Felipe Leme37f83722018-08-16 13:12:03 -0700138 private SparseArray<InternalOnClickAction> mActions;
Felipe Leme979013d2017-06-22 10:59:23 -0700139
140 /**
141 * Default constructor.
142 *
Felipe Lemec24a56a2017-08-03 14:27:57 -0700143 * <p><b>Note:</b> If any child view of presentation triggers a
144 * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent) pending intent
145 * on click}, such {@link PendingIntent} must follow the restrictions below, otherwise
Felipe Leme2c888422017-10-26 12:46:35 -0700146 * it might not be triggered or the autofill save UI might not be shown when its activity
Felipe Lemec24a56a2017-08-03 14:27:57 -0700147 * is finished:
148 * <ul>
149 * <li>It cannot be created with the {@link PendingIntent#FLAG_IMMUTABLE} flag.
150 * <li>It must be a PendingIntent for an {@link Activity}.
151 * <li>The activity must call {@link Activity#finish()} when done.
152 * <li>The activity should not launch other activities.
153 * </ul>
154 *
Felipe Leme979013d2017-06-22 10:59:23 -0700155 * @param parentPresentation template presentation with (optional) children views.
Felipe Leme63c601a2017-09-27 17:40:30 -0700156 * @throws NullPointerException if {@code parentPresentation} is null (on Android
157 * {@link android.os.Build.VERSION_CODES#P} or higher).
Felipe Leme979013d2017-06-22 10:59:23 -0700158 */
Felipe Leme63c601a2017-09-27 17:40:30 -0700159 public Builder(@NonNull RemoteViews parentPresentation) {
160 mPresentation = Preconditions.checkNotNull(parentPresentation);
Felipe Leme979013d2017-06-22 10:59:23 -0700161 }
162
163 /**
164 * Adds a transformation to replace the value of a child view with the fields in the
165 * screen.
166 *
Felipe Lemededf8f12017-08-10 13:46:14 -0700167 * <p>When multiple transformations are added for the same child view, they will be applied
168 * in the same order as added.
169 *
Felipe Leme979013d2017-06-22 10:59:23 -0700170 * @param id view id of the children view.
171 * @param transformation an implementation provided by the Android System.
Felipe Leme37f83722018-08-16 13:12:03 -0700172 *
Felipe Leme979013d2017-06-22 10:59:23 -0700173 * @return this builder.
Felipe Leme37f83722018-08-16 13:12:03 -0700174 *
Felipe Leme979013d2017-06-22 10:59:23 -0700175 * @throws IllegalArgumentException if {@code transformation} is not a class provided
176 * by the Android System.
Felipe Leme37f83722018-08-16 13:12:03 -0700177 * @throws IllegalStateException if {@link #build()} was already called.
Felipe Leme979013d2017-06-22 10:59:23 -0700178 */
Felipe Leme8e156a62019-03-01 17:16:40 -0800179 @NonNull
Felipe Leme979013d2017-06-22 10:59:23 -0700180 public Builder addChild(int id, @NonNull Transformation transformation) {
Felipe Leme63c601a2017-09-27 17:40:30 -0700181 throwIfDestroyed();
Felipe Leme979013d2017-06-22 10:59:23 -0700182 Preconditions.checkArgument((transformation instanceof InternalTransformation),
183 "not provided by Android System: " + transformation);
184 if (mTransformations == null) {
Felipe Lemededf8f12017-08-10 13:46:14 -0700185 mTransformations = new ArrayList<>();
Felipe Leme979013d2017-06-22 10:59:23 -0700186 }
Felipe Lemededf8f12017-08-10 13:46:14 -0700187 mTransformations.add(new Pair<>(id, (InternalTransformation) transformation));
Felipe Leme979013d2017-06-22 10:59:23 -0700188 return this;
189 }
190
191 /**
Felipe Lemec83abcb2018-01-30 10:25:58 -0800192 * Updates the {@link RemoteViews presentation template} when a condition is satisfied by
193 * applying a series of remote view operations. This allows dynamic customization of the
194 * portion of the save UI that is controlled by the autofill service. Such dynamic
195 * customization is based on the content of target views.
Felipe Leme63c601a2017-09-27 17:40:30 -0700196 *
197 * <p>The updates are applied in the sequence they are added, after the
198 * {@link #addChild(int, Transformation) transformations} are applied to the children
199 * views.
200 *
201 * <p>For example, to make children views visible when fields are not empty:
202 *
203 * <pre class="prettyprint">
204 * RemoteViews template = new RemoteViews(pgkName, R.layout.my_full_template);
205 *
206 * Pattern notEmptyPattern = Pattern.compile(".+");
207 * Validator hasAddress = new RegexValidator(addressAutofillId, notEmptyPattern);
208 * Validator hasCcNumber = new RegexValidator(ccNumberAutofillId, notEmptyPattern);
209 *
210 * RemoteViews addressUpdates = new RemoteViews(pgkName, R.layout.my_full_template)
211 * addressUpdates.setViewVisibility(R.id.address, View.VISIBLE);
212 *
213 * // Make address visible
214 * BatchUpdates addressBatchUpdates = new BatchUpdates.Builder()
215 * .updateTemplate(addressUpdates)
216 * .build();
217 *
218 * RemoteViews ccUpdates = new RemoteViews(pgkName, R.layout.my_full_template)
219 * ccUpdates.setViewVisibility(R.id.cc_number, View.VISIBLE);
220 *
221 * // Mask credit card number (as .....LAST_4_DIGITS) and make it visible
222 * BatchUpdates ccBatchUpdates = new BatchUpdates.Builder()
223 * .updateTemplate(ccUpdates)
224 * .transformChild(R.id.templateCcNumber, new CharSequenceTransformation
225 * .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
226 * .build())
227 * .build();
228 *
229 * CustomDescription customDescription = new CustomDescription.Builder(template)
230 * .batchUpdate(hasAddress, addressBatchUpdates)
231 * .batchUpdate(hasCcNumber, ccBatchUpdates)
232 * .build();
233 * </pre>
234 *
235 * <p>Another approach is to add a child first, then apply the transformations. Example:
236 *
237 * <pre class="prettyprint">
238 * RemoteViews template = new RemoteViews(pgkName, R.layout.my_base_template);
239 *
240 * RemoteViews addressPresentation = new RemoteViews(pgkName, R.layout.address)
241 * RemoteViews addressUpdates = new RemoteViews(pgkName, R.layout.my_template)
242 * addressUpdates.addView(R.id.parentId, addressPresentation);
243 * BatchUpdates addressBatchUpdates = new BatchUpdates.Builder()
244 * .updateTemplate(addressUpdates)
245 * .build();
246 *
247 * RemoteViews ccPresentation = new RemoteViews(pgkName, R.layout.cc)
248 * RemoteViews ccUpdates = new RemoteViews(pgkName, R.layout.my_template)
249 * ccUpdates.addView(R.id.parentId, ccPresentation);
250 * BatchUpdates ccBatchUpdates = new BatchUpdates.Builder()
251 * .updateTemplate(ccUpdates)
252 * .transformChild(R.id.templateCcNumber, new CharSequenceTransformation
253 * .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
254 * .build())
255 * .build();
256 *
257 * CustomDescription customDescription = new CustomDescription.Builder(template)
258 * .batchUpdate(hasAddress, addressBatchUpdates)
259 * .batchUpdate(hasCcNumber, ccBatchUpdates)
260 * .build();
261 * </pre>
262 *
263 * @param condition condition used to trigger the updates.
264 * @param updates actions to be applied to the
265 * {@link #CustomDescription.Builder(RemoteViews) template presentation} when the condition
266 * is satisfied.
267 *
268 * @return this builder
Felipe Leme37f83722018-08-16 13:12:03 -0700269 *
Felipe Leme63c601a2017-09-27 17:40:30 -0700270 * @throws IllegalArgumentException if {@code condition} is not a class provided
271 * by the Android System.
Felipe Leme37f83722018-08-16 13:12:03 -0700272 * @throws IllegalStateException if {@link #build()} was already called.
Felipe Leme63c601a2017-09-27 17:40:30 -0700273 */
Felipe Leme8e156a62019-03-01 17:16:40 -0800274 @NonNull
Felipe Leme63c601a2017-09-27 17:40:30 -0700275 public Builder batchUpdate(@NonNull Validator condition, @NonNull BatchUpdates updates) {
276 throwIfDestroyed();
277 Preconditions.checkArgument((condition instanceof InternalValidator),
278 "not provided by Android System: " + condition);
279 Preconditions.checkNotNull(updates);
280 if (mUpdates == null) {
281 mUpdates = new ArrayList<>();
282 }
283 mUpdates.add(new Pair<>((InternalValidator) condition, updates));
284 return this;
285 }
286
287 /**
Felipe Leme37f83722018-08-16 13:12:03 -0700288 * Sets an action to be applied to the {@link RemoteViews presentation template} when the
289 * child view with the given {@code id} is clicked.
290 *
291 * <p>Typically used when the presentation uses a masked field (like {@code ****}) for
292 * sensitive fields like passwords or credit cards numbers, but offers a an icon that the
293 * user can tap to show the value for that field.
294 *
295 * <p>Example:
296 *
297 * <pre class="prettyprint">
298 * customDescriptionBuilder
299 * .addChild(R.id.password_plain, new CharSequenceTransformation
300 * .Builder(passwordId, Pattern.compile("^(.*)$"), "$1").build())
301 * .addOnClickAction(R.id.showIcon, new VisibilitySetterAction
302 * .Builder(R.id.hideIcon, View.VISIBLE)
303 * .setVisibility(R.id.showIcon, View.GONE)
304 * .setVisibility(R.id.password_plain, View.VISIBLE)
305 * .setVisibility(R.id.password_masked, View.GONE)
306 * .build())
307 * .addOnClickAction(R.id.hideIcon, new VisibilitySetterAction
308 * .Builder(R.id.showIcon, View.VISIBLE)
309 * .setVisibility(R.id.hideIcon, View.GONE)
310 * .setVisibility(R.id.password_masked, View.VISIBLE)
311 * .setVisibility(R.id.password_plain, View.GONE)
312 * .build());
313 * </pre>
314 *
315 * <p><b>Note:</b> Currently only one action can be applied to a child; if this method
316 * is called multiple times passing the same {@code id}, only the last call will be used.
317 *
318 * @param id resource id of the child view.
Felipe Lemed5225e12018-09-13 10:20:12 -0700319 * @param action action to be performed. Must be an an implementation provided by the
320 * Android System.
Felipe Leme37f83722018-08-16 13:12:03 -0700321 *
322 * @return this builder
323 *
324 * @throws IllegalArgumentException if {@code action} is not a class provided
325 * by the Android System.
326 * @throws IllegalStateException if {@link #build()} was already called.
327 */
Felipe Leme8e156a62019-03-01 17:16:40 -0800328 @NonNull
Felipe Leme37f83722018-08-16 13:12:03 -0700329 public Builder addOnClickAction(int id, @NonNull OnClickAction action) {
330 throwIfDestroyed();
331 Preconditions.checkArgument((action instanceof InternalOnClickAction),
332 "not provided by Android System: " + action);
333 if (mActions == null) {
334 mActions = new SparseArray<InternalOnClickAction>();
335 }
336 mActions.put(id, (InternalOnClickAction) action);
337
338 return this;
339 }
340
341 /**
Felipe Leme979013d2017-06-22 10:59:23 -0700342 * Creates a new {@link CustomDescription} instance.
343 */
Felipe Leme8e156a62019-03-01 17:16:40 -0800344 @NonNull
Felipe Leme979013d2017-06-22 10:59:23 -0700345 public CustomDescription build() {
Felipe Leme63c601a2017-09-27 17:40:30 -0700346 throwIfDestroyed();
347 mDestroyed = true;
Felipe Leme979013d2017-06-22 10:59:23 -0700348 return new CustomDescription(this);
349 }
Felipe Leme63c601a2017-09-27 17:40:30 -0700350
351 private void throwIfDestroyed() {
352 if (mDestroyed) {
353 throw new IllegalStateException("Already called #build()");
354 }
355 }
Felipe Leme979013d2017-06-22 10:59:23 -0700356 }
357
358 /////////////////////////////////////
359 // Object "contract" methods. //
360 /////////////////////////////////////
361 @Override
362 public String toString() {
363 if (!sDebug) return super.toString();
364
365 return new StringBuilder("CustomDescription: [presentation=")
366 .append(mPresentation)
Felipe Leme63c601a2017-09-27 17:40:30 -0700367 .append(", transformations=")
368 .append(mTransformations == null ? "N/A" : mTransformations.size())
369 .append(", updates=")
370 .append(mUpdates == null ? "N/A" : mUpdates.size())
Felipe Leme37f83722018-08-16 13:12:03 -0700371 .append(", actions=")
372 .append(mActions == null ? "N/A" : mActions.size())
Felipe Leme979013d2017-06-22 10:59:23 -0700373 .append("]").toString();
374 }
375
376 /////////////////////////////////////
377 // Parcelable "contract" methods. //
378 /////////////////////////////////////
379 @Override
380 public int describeContents() {
381 return 0;
382 }
383
384 @Override
385 public void writeToParcel(Parcel dest, int flags) {
386 dest.writeParcelable(mPresentation, flags);
Felipe Leme63c601a2017-09-27 17:40:30 -0700387 if (mPresentation == null) return;
388
Felipe Leme979013d2017-06-22 10:59:23 -0700389 if (mTransformations == null) {
390 dest.writeIntArray(null);
391 } else {
392 final int size = mTransformations.size();
393 final int[] ids = new int[size];
394 final InternalTransformation[] values = new InternalTransformation[size];
395 for (int i = 0; i < size; i++) {
Felipe Lemededf8f12017-08-10 13:46:14 -0700396 final Pair<Integer, InternalTransformation> pair = mTransformations.get(i);
397 ids[i] = pair.first;
398 values[i] = pair.second;
Felipe Leme979013d2017-06-22 10:59:23 -0700399 }
400 dest.writeIntArray(ids);
401 dest.writeParcelableArray(values, flags);
402 }
Felipe Leme63c601a2017-09-27 17:40:30 -0700403 if (mUpdates == null) {
404 dest.writeParcelableArray(null, flags);
405 } else {
406 final int size = mUpdates.size();
407 final InternalValidator[] conditions = new InternalValidator[size];
408 final BatchUpdates[] updates = new BatchUpdates[size];
409
410 for (int i = 0; i < size; i++) {
411 final Pair<InternalValidator, BatchUpdates> pair = mUpdates.get(i);
412 conditions[i] = pair.first;
413 updates[i] = pair.second;
414 }
415 dest.writeParcelableArray(conditions, flags);
416 dest.writeParcelableArray(updates, flags);
417 }
Felipe Leme37f83722018-08-16 13:12:03 -0700418 if (mActions == null) {
419 dest.writeIntArray(null);
420 } else {
421 final int size = mActions.size();
422 final int[] ids = new int[size];
423 final InternalOnClickAction[] values = new InternalOnClickAction[size];
424 for (int i = 0; i < size; i++) {
425 ids[i] = mActions.keyAt(i);
426 values[i] = mActions.valueAt(i);
427 }
428 dest.writeIntArray(ids);
429 dest.writeParcelableArray(values, flags);
430 }
Felipe Leme979013d2017-06-22 10:59:23 -0700431 }
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700432 public static final @android.annotation.NonNull Parcelable.Creator<CustomDescription> CREATOR =
Felipe Leme979013d2017-06-22 10:59:23 -0700433 new Parcelable.Creator<CustomDescription>() {
434 @Override
435 public CustomDescription createFromParcel(Parcel parcel) {
436 // Always go through the builder to ensure the data ingested by
437 // the system obeys the contract of the builder to avoid attacks
438 // using specially crafted parcels.
Felipe Leme63c601a2017-09-27 17:40:30 -0700439 final RemoteViews parentPresentation = parcel.readParcelable(null);
440 if (parentPresentation == null) return null;
441
442 final Builder builder = new Builder(parentPresentation);
Felipe Leme37f83722018-08-16 13:12:03 -0700443 final int[] transformationIds = parcel.createIntArray();
444 if (transformationIds != null) {
Felipe Leme979013d2017-06-22 10:59:23 -0700445 final InternalTransformation[] values =
446 parcel.readParcelableArray(null, InternalTransformation.class);
Felipe Leme37f83722018-08-16 13:12:03 -0700447 final int size = transformationIds.length;
Felipe Leme979013d2017-06-22 10:59:23 -0700448 for (int i = 0; i < size; i++) {
Felipe Leme37f83722018-08-16 13:12:03 -0700449 builder.addChild(transformationIds[i], values[i]);
Felipe Leme979013d2017-06-22 10:59:23 -0700450 }
451 }
Felipe Leme63c601a2017-09-27 17:40:30 -0700452 final InternalValidator[] conditions =
453 parcel.readParcelableArray(null, InternalValidator.class);
454 if (conditions != null) {
455 final BatchUpdates[] updates = parcel.readParcelableArray(null, BatchUpdates.class);
456 final int size = conditions.length;
457 for (int i = 0; i < size; i++) {
458 builder.batchUpdate(conditions[i], updates[i]);
459 }
460 }
Felipe Leme37f83722018-08-16 13:12:03 -0700461 final int[] actionIds = parcel.createIntArray();
462 if (actionIds != null) {
463 final InternalOnClickAction[] values =
464 parcel.readParcelableArray(null, InternalOnClickAction.class);
465 final int size = actionIds.length;
466 for (int i = 0; i < size; i++) {
467 builder.addOnClickAction(actionIds[i], values[i]);
468 }
469 }
Felipe Leme979013d2017-06-22 10:59:23 -0700470 return builder.build();
471 }
472
473 @Override
474 public CustomDescription[] newArray(int size) {
475 return new CustomDescription[size];
476 }
477 };
478}