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 * —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— 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