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