SaveInfo.java revision 6a2ec5b5187f46b4509da75ebc527aca78b7acb4
1/*
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.IntDef;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.app.Activity;
25import android.content.IntentSender;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.util.DebugUtils;
29import android.view.autofill.AutofillId;
30import android.view.autofill.AutofillManager;
31import android.view.autofill.AutofillValue;
32
33import com.android.internal.util.ArrayUtils;
34import com.android.internal.util.Preconditions;
35
36import java.lang.annotation.Retention;
37import java.lang.annotation.RetentionPolicy;
38import java.util.Arrays;
39
40/**
41 * Information used to indicate that an {@link AutofillService} is interested on saving the
42 * user-inputed data for future use, through a
43 * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
44 * call.
45 *
46 * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least
47 * two pieces of information:
48 *
49 * <ol>
50 *   <li>The type(s) of user data (like password or credit card info) that would be saved.
51 *   <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed
52 *       to trigger a save request.
53 * </ol>
54 *
55 * <p>Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}:
56 *
57 * <pre class="prettyprint">
58 *   new FillResponse.Builder()
59 *       .addDataset(new Dataset.Builder()
60 *           .setValue(id1, AutofillValue.forText("homer"), createPresentation("homer")) // username
61 *           .setValue(id2, AutofillValue.forText("D'OH!"), createPresentation("password for homer")) // password
62 *           .build())
63 *       .setSaveInfo(new SaveInfo.Builder(
64 *           SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
65 *           new AutofillId[] { id1, id2 }).build())
66 *       .build();
67 * </pre>
68 *
69 * <p>The save type flags are used to display the appropriate strings in the save UI affordance.
70 * You can pass multiple values, but try to keep it short if possible. In the above example, just
71 * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough.
72 *
73 * <p>There might be cases where the {@link AutofillService} knows how to fill the screen,
74 * but the user has no data for it. In that case, the {@link FillResponse} should contain just the
75 * {@link SaveInfo}, but no {@link Dataset Datasets}:
76 *
77 * <pre class="prettyprint">
78 *   new FillResponse.Builder()
79 *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_PASSWORD,
80 *           new AutofillId[] { id1, id2 }).build())
81 *       .build();
82 * </pre>
83 *
84 * <p>There might be cases where the user data in the {@link AutofillService} is enough
85 * to populate some fields but not all, and the service would still be interested on saving the
86 * other fields. In that case, the service could set the
87 * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well:
88 *
89 * <pre class="prettyprint">
90 *   new FillResponse.Builder()
91 *       .addDataset(new Dataset.Builder()
92 *           .setValue(id1, AutofillValue.forText("742 Evergreen Terrace"),
93 *               createPresentation("742 Evergreen Terrace")) // street
94 *           .setValue(id2, AutofillValue.forText("Springfield"),
95 *               createPresentation("Springfield")) // city
96 *           .build())
97 *       .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS,
98 *           new AutofillId[] { id1, id2 }) // street and  city
99 *           .setOptionalIds(new AutofillId[] { id3, id4 }) // state and zipcode
100 *           .build())
101 *       .build();
102 * </pre>
103 *
104 * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after
105 * any of the following events:
106 * <ul>
107 *   <li>The {@link Activity} finishes.
108 *   <li>The app explicitly called {@link AutofillManager#commit()}.
109 *   <li>All required views became invisible (if the {@link SaveInfo} was created with the
110 *       {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag).
111 * </ul>
112 *
113 * <p>But it is only triggered when all conditions below are met:
114 * <ul>
115 *   <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null}.
116 *   <li>The {@link AutofillValue}s of all required views (as set by the {@code requiredIds} passed
117 *       to the {@link SaveInfo.Builder} constructor are not empty.
118 *   <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed
119 *       (i.e., it's neither the same value passed in a {@link Dataset}, nor the initial value
120 *       presented in the view).
121 *   <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the
122 *       screen state (i.e., all required and optional fields in the dataset have the same value as
123 *       the fields in the screen).
124 *   <li>The user explicitly tapped the UI affordance asking to save data for autofill.
125 * </ul>
126 *
127 * <p>The service can also customize some aspects of the save UI affordance:
128 * <ul>
129 *   <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}.
130 *   <li>Add a customized subtitle by calling
131 *       {@link Builder#setCustomDescription(CustomDescription)}.
132 *   <li>Customize the button used to reject the save request by calling
133 *       {@link Builder#setNegativeAction(int, IntentSender)}.
134 *   <li>Decide whether the UI should be shown based on the user input validation by calling
135 *       {@link Builder#setValidator(Validator)}.
136 * </ul>
137 */
138public final class SaveInfo implements Parcelable {
139
140    /**
141     * Type used when the service can save the contents of a screen, but cannot describe what
142     * the content is for.
143     */
144    public static final int SAVE_DATA_TYPE_GENERIC = 0x0;
145
146    /**
147     * Type used when the {@link FillResponse} represents user credentials that have a password.
148     */
149    public static final int SAVE_DATA_TYPE_PASSWORD = 0x01;
150
151    /**
152     * Type used on when the {@link FillResponse} represents a physical address (such as street,
153     * city, state, etc).
154     */
155    public static final int SAVE_DATA_TYPE_ADDRESS = 0x02;
156
157    /**
158     * Type used when the {@link FillResponse} represents a credit card.
159     */
160    public static final int SAVE_DATA_TYPE_CREDIT_CARD = 0x04;
161
162    /**
163     * Type used when the {@link FillResponse} represents just an username, without a password.
164     */
165    public static final int SAVE_DATA_TYPE_USERNAME = 0x08;
166
167    /**
168     * Type used when the {@link FillResponse} represents just an email address, without a password.
169     */
170    public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 0x10;
171
172    /**
173     * Style for the negative button of the save UI to cancel the
174     * save operation. In this case, the user tapping the negative
175     * button signals that they would prefer to not save the filled
176     * content.
177     */
178    public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0;
179
180    /**
181     * Style for the negative button of the save UI to reject the
182     * save operation. This could be useful if the user needs to
183     * opt-in your service and the save prompt is an advertisement
184     * of the potential value you can add to the user. In this
185     * case, the user tapping the negative button sends a strong
186     * signal that the feature may not be useful and you may
187     * consider some backoff strategy.
188     */
189    public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1;
190
191    /** @hide */
192    @IntDef(
193        value = {
194                NEGATIVE_BUTTON_STYLE_CANCEL,
195                NEGATIVE_BUTTON_STYLE_REJECT})
196    @Retention(RetentionPolicy.SOURCE)
197    @interface NegativeButtonStyle{}
198
199    /** @hide */
200    @IntDef(
201       flag = true,
202       value = {
203               SAVE_DATA_TYPE_GENERIC,
204               SAVE_DATA_TYPE_PASSWORD,
205               SAVE_DATA_TYPE_ADDRESS,
206               SAVE_DATA_TYPE_CREDIT_CARD,
207               SAVE_DATA_TYPE_USERNAME,
208               SAVE_DATA_TYPE_EMAIL_ADDRESS})
209    @Retention(RetentionPolicy.SOURCE)
210    @interface SaveDataType{}
211
212    /**
213     * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
214     * is called once the {@link Activity} finishes. If this flag is set it is called once all
215     * saved views become invisible.
216     */
217    public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
218
219    /** @hide */
220    @IntDef(
221            flag = true,
222            value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE})
223    @Retention(RetentionPolicy.SOURCE)
224    @interface SaveInfoFlags{}
225
226    private final @SaveDataType int mType;
227    private final @NegativeButtonStyle int mNegativeButtonStyle;
228    private final IntentSender mNegativeActionListener;
229    private final AutofillId[] mRequiredIds;
230    private final AutofillId[] mOptionalIds;
231    private final CharSequence mDescription;
232    private final int mFlags;
233    private final CustomDescription mCustomDescription;
234    private final InternalValidator mValidator;
235
236    private SaveInfo(Builder builder) {
237        mType = builder.mType;
238        mNegativeButtonStyle = builder.mNegativeButtonStyle;
239        mNegativeActionListener = builder.mNegativeActionListener;
240        mRequiredIds = builder.mRequiredIds;
241        mOptionalIds = builder.mOptionalIds;
242        mDescription = builder.mDescription;
243        mFlags = builder.mFlags;
244        mCustomDescription = builder.mCustomDescription;
245        mValidator = builder.mValidator;
246    }
247
248    /** @hide */
249    public @NegativeButtonStyle int getNegativeActionStyle() {
250        return mNegativeButtonStyle;
251    }
252
253    /** @hide */
254    public @Nullable IntentSender getNegativeActionListener() {
255        return mNegativeActionListener;
256    }
257
258    /** @hide */
259    public @Nullable AutofillId[] getRequiredIds() {
260        return mRequiredIds;
261    }
262
263    /** @hide */
264    public @Nullable AutofillId[] getOptionalIds() {
265        return mOptionalIds;
266    }
267
268    /** @hide */
269    public @SaveDataType int getType() {
270        return mType;
271    }
272
273    /** @hide */
274    public @SaveInfoFlags int getFlags() {
275        return mFlags;
276    }
277
278    /** @hide */
279    public CharSequence getDescription() {
280        return mDescription;
281    }
282
283     /** @hide */
284    @Nullable
285    public CustomDescription getCustomDescription() {
286        return mCustomDescription;
287    }
288
289    /** @hide */
290    @Nullable
291    public InternalValidator getValidator() {
292        return mValidator;
293    }
294
295    /**
296     * A builder for {@link SaveInfo} objects.
297     */
298    public static final class Builder {
299
300        private final @SaveDataType int mType;
301        private @NegativeButtonStyle int mNegativeButtonStyle = NEGATIVE_BUTTON_STYLE_CANCEL;
302        private IntentSender mNegativeActionListener;
303        private final AutofillId[] mRequiredIds;
304        private AutofillId[] mOptionalIds;
305        private CharSequence mDescription;
306        private boolean mDestroyed;
307        private int mFlags;
308        private CustomDescription mCustomDescription;
309        private InternalValidator mValidator;
310
311        /**
312         * Creates a new builder.
313         *
314         * @param type the type of information the associated {@link FillResponse} represents. It
315         * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
316         * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
317         * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
318         * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
319         * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
320         * @param requiredIds ids of all required views that will trigger a save request.
321         *
322         * <p>See {@link SaveInfo} for more info.
323         *
324         * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty, or if
325         * it contains any {@code null} entry.
326         */
327        public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
328            mType = type;
329            mRequiredIds = assertValid(requiredIds);
330        }
331
332        /**
333         * Creates a new builder when no id is required.
334         *
335         * <p>When using this builder, caller must call {@link #setOptionalIds(AutofillId[])} before
336         * calling {@link #build()}.
337         *
338         * @param type the type of information the associated {@link FillResponse} represents. It
339         * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
340         * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
341         * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
342         * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
343         * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
344         *
345         * <p>See {@link SaveInfo} for more info.
346         */
347        public Builder(@SaveDataType int type) {
348            mType = type;
349            mRequiredIds = null;
350        }
351
352        private AutofillId[] assertValid(AutofillId[] ids) {
353            Preconditions.checkArgument(ids != null && ids.length > 0,
354                    "must have at least one id: " + Arrays.toString(ids));
355            for (int i = 0; i < ids.length; i++) {
356                final AutofillId id = ids[i];
357                Preconditions.checkArgument(id != null,
358                        "cannot have null id: " + Arrays.toString(ids));
359            }
360            return ids;
361        }
362
363        /**
364         * Sets flags changing the save behavior.
365         *
366         * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}.
367         * @return This builder.
368         */
369        public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
370            throwIfDestroyed();
371
372            mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
373            return this;
374        }
375
376        /**
377         * Sets the ids of additional, optional views the service would be interested to save.
378         *
379         * <p>See {@link SaveInfo} for more info.
380         *
381         * @param ids The ids of the optional views.
382         * @return This builder.
383         *
384         * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
385         * it contains any {@code null} entry.
386         */
387        public @NonNull Builder setOptionalIds(@NonNull AutofillId[] ids) {
388            throwIfDestroyed();
389            mOptionalIds = assertValid(ids);
390            return this;
391        }
392
393        /**
394         * Sets an optional description to be shown in the UI when the user is asked to save.
395         *
396         * <p>Typically, it describes how the data will be stored by the service, so it can help
397         * users to decide whether they can trust the service to save their data.
398         *
399         * @param description a succint description.
400         * @return This Builder.
401         *
402         * @throws IllegalStateException if this call was made after calling
403         * {@link #setCustomDescription(CustomDescription)}.
404         */
405        public @NonNull Builder setDescription(@Nullable CharSequence description) {
406            throwIfDestroyed();
407            Preconditions.checkState(mCustomDescription == null,
408                    "Can call setDescription() or setCustomDescription(), but not both");
409            mDescription = description;
410            return this;
411        }
412
413        /**
414         * Sets a custom description to be shown in the UI when the user is asked to save.
415         *
416         * <p>Typically used when the service must show more info about the object being saved,
417         * like a credit card logo, masked number, and expiration date.
418         *
419         * @param customDescription the custom description.
420         * @return This Builder.
421         *
422         * @throws IllegalStateException if this call was made after calling
423         * {@link #setDescription(CharSequence)}.
424         */
425        public @NonNull Builder setCustomDescription(@NonNull CustomDescription customDescription) {
426            throwIfDestroyed();
427            Preconditions.checkState(mDescription == null,
428                    "Can call setDescription() or setCustomDescription(), but not both");
429            mCustomDescription = customDescription;
430            return this;
431        }
432
433        /**
434         * Sets the style and listener for the negative save action.
435         *
436         * <p>This allows an autofill service to customize the style and be
437         * notified when the user selects the negative action in the save
438         * UI. Note that selecting the negative action regardless of its style
439         * and listener being customized would dismiss the save UI and if a
440         * custom listener intent is provided then this intent is
441         * started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p>
442         *
443         * @param style The action style.
444         * @param listener The action listener.
445         * @return This builder.
446         *
447         * @see #NEGATIVE_BUTTON_STYLE_CANCEL
448         * @see #NEGATIVE_BUTTON_STYLE_REJECT
449         *
450         * @throws IllegalArgumentException If the style is invalid
451         */
452        public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style,
453                @Nullable IntentSender listener) {
454            throwIfDestroyed();
455            if (style != NEGATIVE_BUTTON_STYLE_CANCEL
456                    && style != NEGATIVE_BUTTON_STYLE_REJECT) {
457                throw new IllegalArgumentException("Invalid style: " + style);
458            }
459            mNegativeButtonStyle = style;
460            mNegativeActionListener = listener;
461            return this;
462        }
463
464        /**
465         * Sets an object used to validate the user input - if the input is not valid, the Save UI
466         * affordance is not shown.
467         *
468         * <p>Typically used to validate credit card numbers. Examples:
469         *
470         * <p>Validator for a credit number that must have exactly 16 digits:
471         *
472         * <pre class="prettyprint">
473         * Validator validator = new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$"))
474         * </pre>
475         *
476         * <p>Validator for a credit number that must pass a Luhn checksum and either have
477         * 16 digits, or 15 digits starting with 108:
478         *
479         * <pre class="prettyprint">
480         * import static android.service.autofill.Validators.and;
481         * import static android.service.autofill.Validators.or;
482         *
483         * Validator validator =
484         *   and(
485         *     new LuhnChecksumValidator(ccNumberId),
486         *     or(
487         *       new RegexValidator(ccNumberId, Pattern.compile("^\\d{16}$")),
488         *       new RegexValidator(ccNumberId, Pattern.compile("^108\\d{12}$"))
489         *     )
490         *   );
491         * </pre>
492         *
493         * <p><b>NOTE: </b>the example above is just for illustrative purposes; the same validator
494         * could be created using a single regex for the {@code OR} part:
495         *
496         * <pre class="prettyprint">
497         * Validator validator =
498         *   and(
499         *     new LuhnChecksumValidator(ccNumberId),
500         *     new RegexValidator(ccNumberId, Pattern.compile(""^(\\d{16}|108\\d{12})$"))
501         *   );
502         * </pre>
503         *
504         * <p>Validator for a credit number contained in just 4 fields and that must have exactly
505         * 4 digits on each field:
506         *
507         * <pre class="prettyprint">
508         * import static android.service.autofill.Validators.and;
509         *
510         * Validator validator =
511         *   and(
512         *     new RegexValidator(ccNumberId1, Pattern.compile("^\\d{4}$")),
513         *     new RegexValidator(ccNumberId2, Pattern.compile("^\\d{4}$")),
514         *     new RegexValidator(ccNumberId3, Pattern.compile("^\\d{4}$")),
515         *     new RegexValidator(ccNumberId4, Pattern.compile("^\\d{4}$"))
516         *   );
517         * </pre>
518         *
519         * @param validator an implementation provided by the Android System.
520         * @return this builder.
521         *
522         * @throws IllegalArgumentException if {@code validator} is not a class provided
523         * by the Android System.
524         */
525        public @NonNull Builder setValidator(@NonNull Validator validator) {
526            throwIfDestroyed();
527            Preconditions.checkArgument((validator instanceof InternalValidator),
528                    "not provided by Android System: " + validator);
529            mValidator = (InternalValidator) validator;
530            return this;
531        }
532
533        /**
534         * Builds a new {@link SaveInfo} instance.
535         *
536         * @throws IllegalStateException if no
537         * {@link #SaveInfo.Builder(int, AutofillId[]) required ids}
538         * or {@link #setOptionalIds(AutofillId[]) optional ids} were set
539         */
540        public SaveInfo build() {
541            throwIfDestroyed();
542            Preconditions.checkState(
543                    !ArrayUtils.isEmpty(mRequiredIds) || !ArrayUtils.isEmpty(mOptionalIds),
544                    "must have at least one required or optional id");
545            mDestroyed = true;
546            return new SaveInfo(this);
547        }
548
549        private void throwIfDestroyed() {
550            if (mDestroyed) {
551                throw new IllegalStateException("Already called #build()");
552            }
553        }
554    }
555
556    /////////////////////////////////////
557    // Object "contract" methods. //
558    /////////////////////////////////////
559    @Override
560    public String toString() {
561        if (!sDebug) return super.toString();
562
563        return new StringBuilder("SaveInfo: [type=")
564                .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType))
565                .append(", requiredIds=").append(Arrays.toString(mRequiredIds))
566                .append(", optionalIds=").append(Arrays.toString(mOptionalIds))
567                .append(", description=").append(mDescription)
568                .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
569                        mNegativeButtonStyle))
570                .append(", mFlags=").append(mFlags)
571                .append(", mCustomDescription=").append(mCustomDescription)
572                .append(", validation=").append(mValidator)
573                .append("]").toString();
574    }
575
576    /////////////////////////////////////
577    // Parcelable "contract" methods. //
578    /////////////////////////////////////
579
580    @Override
581    public int describeContents() {
582        return 0;
583    }
584
585    @Override
586    public void writeToParcel(Parcel parcel, int flags) {
587        parcel.writeInt(mType);
588        parcel.writeParcelableArray(mRequiredIds, flags);
589        parcel.writeParcelableArray(mOptionalIds, flags);
590        parcel.writeInt(mNegativeButtonStyle);
591        parcel.writeParcelable(mNegativeActionListener, flags);
592        parcel.writeCharSequence(mDescription);
593        parcel.writeParcelable(mCustomDescription, flags);
594        parcel.writeParcelable(mValidator, flags);
595        parcel.writeInt(mFlags);
596    }
597
598    public static final Parcelable.Creator<SaveInfo> CREATOR = new Parcelable.Creator<SaveInfo>() {
599        @Override
600        public SaveInfo createFromParcel(Parcel parcel) {
601
602            // Always go through the builder to ensure the data ingested by
603            // the system obeys the contract of the builder to avoid attacks
604            // using specially crafted parcels.
605            final int type = parcel.readInt();
606            final AutofillId[] requiredIds = parcel.readParcelableArray(null, AutofillId.class);
607            final Builder builder = requiredIds != null
608                    ? new Builder(type, requiredIds)
609                    : new Builder(type);
610            final AutofillId[] optionalIds = parcel.readParcelableArray(null, AutofillId.class);
611            if (optionalIds != null) {
612                builder.setOptionalIds(optionalIds);
613            }
614
615            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
616            builder.setDescription(parcel.readCharSequence());
617            final CustomDescription customDescripton = parcel.readParcelable(null);
618            if (customDescripton != null) {
619                builder.setCustomDescription(customDescripton);
620            }
621            final InternalValidator validator = parcel.readParcelable(null);
622            if (validator != null) {
623                builder.setValidator(validator);
624            }
625            builder.setFlags(parcel.readInt());
626            return builder.build();
627        }
628
629        @Override
630        public SaveInfo[] newArray(int size) {
631            return new SaveInfo[size];
632        }
633    };
634}
635