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