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