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