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