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