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