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