1 /* 2 * Copyright (C) 2016 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.view.autofill.Helper.sDebug; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SuppressLint; 25 import android.annotation.SystemApi; 26 import android.annotation.TestApi; 27 import android.content.ClipData; 28 import android.content.IntentSender; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.util.ArrayMap; 32 import android.view.autofill.AutofillId; 33 import android.view.autofill.AutofillManager; 34 import android.view.autofill.AutofillValue; 35 import android.widget.RemoteViews; 36 37 import com.android.internal.util.Preconditions; 38 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 import java.util.ArrayList; 42 import java.util.Objects; 43 import java.util.regex.Pattern; 44 45 /** 46 * <p>A <code>Dataset</code> object represents a group of fields (key / value pairs) used 47 * to autofill parts of a screen. 48 * 49 * <p>For more information about the role of datasets in the autofill workflow, read 50 * <a href="/guide/topics/text/autofill-services">Build autofill services</a> and the 51 * <code><a href="/reference/android/service/autofill/AutofillService">AutofillService</a></code> 52 * documentation. 53 * 54 * <a name="BasicUsage"></a> 55 * <h3>Basic usage</h3> 56 * 57 * <p>In its simplest form, a dataset contains one or more fields (comprised of 58 * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter 59 * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields 60 * (each field could have its own {@link RemoteViews presentation}, or use the default 61 * {@link RemoteViews presentation} associated with the whole dataset). 62 * 63 * <p>When an autofill service returns datasets in a {@link FillResponse} 64 * and the screen input is focused in a view that is present in at least one of these datasets, 65 * the Android System displays a UI containing the {@link RemoteViews presentation} of 66 * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a 67 * dataset from the UI, all views in that dataset are autofilled. 68 * 69 * <p>If both the current Input Method and autofill service supports inline suggestions, the Dataset 70 * can be shown by the keyboard as a suggestion. To use this feature, the Dataset should contain 71 * an {@link InlinePresentation} representing how the inline suggestion UI will be rendered. 72 * 73 * <a name="FillDialogUI"></a> 74 * <h3>Fill Dialog UI</h3> 75 * 76 * <p>The fill dialog UI is a more conspicuous and efficient interface than dropdown UI. If autofill 77 * suggestions are available when the user clicks on a field that supports filling the dialog UI, 78 * Autofill will pop up a fill dialog. The dialog will take up a larger area to display the 79 * datasets, so it is easy for users to pay attention to the datasets and selecting a dataset. 80 * If the user focuses on the view before suggestions are available, will fall back to dropdown UI 81 * or inline suggestions. 82 * 83 * <a name="Authentication"></a> 84 * <h3>Dataset authentication</h3> 85 * 86 * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates 87 * the dataset—in that case, when a dataset is selected by the user, the Android System 88 * launches an intent set by the service to "unlock" the dataset. 89 * 90 * <p>For example, when a data set contains credit card information (such as number, 91 * expiration date, and verification code), you could provide a dataset presentation saying 92 * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking 93 * the user to enter the credit card code, and if the user enters a valid code, you could then 94 * "unlock" the dataset. 95 * 96 * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example, 97 * if the activity being autofilled is an account creation screen, you could use an authenticated 98 * dataset to automatically generate a random password for the user. 99 * 100 * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset 101 * authentication mechanism. 102 * 103 * <a name="Filtering"></a> 104 * <h3>Filtering</h3> 105 * <p>The autofill UI automatically changes which values are shown based on value of the view 106 * anchoring it, following the rules below: 107 * <ol> 108 * <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not 109 * {@link AutofillValue#isText() text} or is empty, all datasets are shown. 110 * <li>Datasets that have a filter regex (set through {@link Field.Builder#setFilter(Pattern)} 111 * and {@link Dataset.Builder#setField(AutofillId, Field)}) and whose regex matches the view's 112 * text value converted to lower case are shown. 113 * <li>Datasets that do not require authentication, have a field value that is 114 * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts 115 * with the lower case value of the view's text are shown. 116 * <li>All other datasets are hidden. 117 * </ol> 118 */ 119 public final class Dataset implements Parcelable { 120 /** 121 * This dataset is picked because of unknown reason. 122 * @hide 123 */ 124 public static final int PICK_REASON_UNKNOWN = 0; 125 /** 126 * This dataset is picked because pcc wasn't enabled. 127 * @hide 128 */ 129 public static final int PICK_REASON_NO_PCC = 1; 130 /** 131 * This dataset is picked because provider gave this dataset. 132 * @hide 133 */ 134 public static final int PICK_REASON_PROVIDER_DETECTION_ONLY = 2; 135 /** 136 * This dataset is picked because provider detection was preferred. However, provider also made 137 * this dataset available for PCC detected types, so they could've been picked up by PCC 138 * detection. This however doesn't imply that this dataset would've been chosen for sure. For 139 * eg, if PCC Detection was preferred, and PCC detected other field types, which wasn't 140 * applicable to this dataset, it wouldn't have been shown. 141 * @hide 142 */ 143 public static final int PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC = 3; 144 /** 145 * This dataset is picked because of PCC detection was chosen. 146 * @hide 147 */ 148 public static final int PICK_REASON_PCC_DETECTION_ONLY = 4; 149 /** 150 * This dataset is picked because of PCC Detection was preferred. However, Provider also gave 151 * this dataset, so if PCC wasn't enabled, this dataset would've been eligible anyway. 152 * @hide 153 */ 154 public static final int PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER = 5; 155 156 /** 157 * Reason why the dataset was eligible for autofill. 158 * @hide 159 */ 160 @IntDef(prefix = { "PICK_REASON_" }, value = { 161 PICK_REASON_UNKNOWN, 162 PICK_REASON_NO_PCC, 163 PICK_REASON_PROVIDER_DETECTION_ONLY, 164 PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC, 165 PICK_REASON_PCC_DETECTION_ONLY, 166 PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER, 167 }) 168 @Retention(RetentionPolicy.SOURCE) 169 public @interface DatasetEligibleReason{} 170 171 private @DatasetEligibleReason int mEligibleReason; 172 173 private final ArrayList<AutofillId> mFieldIds; 174 private final ArrayList<AutofillValue> mFieldValues; 175 private final ArrayList<RemoteViews> mFieldPresentations; 176 private final ArrayList<RemoteViews> mFieldDialogPresentations; 177 private final ArrayList<InlinePresentation> mFieldInlinePresentations; 178 private final ArrayList<InlinePresentation> mFieldInlineTooltipPresentations; 179 private final ArrayList<DatasetFieldFilter> mFieldFilters; 180 private final ArrayList<String> mAutofillDatatypes; 181 182 @Nullable private final ClipData mFieldContent; 183 private final RemoteViews mPresentation; 184 private final RemoteViews mDialogPresentation; 185 @Nullable private final InlinePresentation mInlinePresentation; 186 @Nullable private final InlinePresentation mInlineTooltipPresentation; 187 private final IntentSender mAuthentication; 188 @Nullable String mId; 189 190 /** 191 * Constructor to copy the dataset, but replaces the AutofillId with the given input. 192 * Useful to modify the field type, and provide autofillId. 193 * @hide 194 */ Dataset( ArrayList<AutofillId> fieldIds, ArrayList<AutofillValue> fieldValues, ArrayList<RemoteViews> fieldPresentations, ArrayList<RemoteViews> fieldDialogPresentations, ArrayList<InlinePresentation> fieldInlinePresentations, ArrayList<InlinePresentation> fieldInlineTooltipPresentations, ArrayList<DatasetFieldFilter> fieldFilters, ArrayList<String> autofillDatatypes, ClipData fieldContent, RemoteViews presentation, RemoteViews dialogPresentation, @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation inlineTooltipPresentation, @Nullable String id, IntentSender authentication)195 public Dataset( 196 ArrayList<AutofillId> fieldIds, 197 ArrayList<AutofillValue> fieldValues, 198 ArrayList<RemoteViews> fieldPresentations, 199 ArrayList<RemoteViews> fieldDialogPresentations, 200 ArrayList<InlinePresentation> fieldInlinePresentations, 201 ArrayList<InlinePresentation> fieldInlineTooltipPresentations, 202 ArrayList<DatasetFieldFilter> fieldFilters, 203 ArrayList<String> autofillDatatypes, 204 ClipData fieldContent, 205 RemoteViews presentation, 206 RemoteViews dialogPresentation, 207 @Nullable InlinePresentation inlinePresentation, 208 @Nullable InlinePresentation inlineTooltipPresentation, 209 @Nullable String id, 210 IntentSender authentication) { 211 mFieldIds = fieldIds; 212 mFieldValues = fieldValues; 213 mFieldPresentations = fieldPresentations; 214 mFieldDialogPresentations = fieldDialogPresentations; 215 mFieldInlinePresentations = fieldInlinePresentations; 216 mFieldInlineTooltipPresentations = fieldInlineTooltipPresentations; 217 mAutofillDatatypes = autofillDatatypes; 218 mFieldFilters = fieldFilters; 219 mFieldContent = fieldContent; 220 mPresentation = presentation; 221 mDialogPresentation = dialogPresentation; 222 mInlinePresentation = inlinePresentation; 223 mInlineTooltipPresentation = inlineTooltipPresentation; 224 mAuthentication = authentication; 225 mId = id; 226 } 227 228 /** 229 * Constructor to copy the dataset, but replaces the AutofillId with the given input. 230 * Useful to modify the field type, and provide autofillId. 231 * @hide 232 */ Dataset(Dataset dataset, ArrayList<AutofillId> ids)233 public Dataset(Dataset dataset, ArrayList<AutofillId> ids) { 234 mFieldIds = ids; 235 mFieldValues = dataset.mFieldValues; 236 mFieldPresentations = dataset.mFieldPresentations; 237 mFieldDialogPresentations = dataset.mFieldDialogPresentations; 238 mFieldInlinePresentations = dataset.mFieldInlinePresentations; 239 mFieldInlineTooltipPresentations = dataset.mFieldInlineTooltipPresentations; 240 mFieldFilters = dataset.mFieldFilters; 241 mFieldContent = dataset.mFieldContent; 242 mPresentation = dataset.mPresentation; 243 mDialogPresentation = dataset.mDialogPresentation; 244 mInlinePresentation = dataset.mInlinePresentation; 245 mInlineTooltipPresentation = dataset.mInlineTooltipPresentation; 246 mAuthentication = dataset.mAuthentication; 247 mId = dataset.mId; 248 mAutofillDatatypes = dataset.mAutofillDatatypes; 249 } 250 Dataset(Builder builder)251 private Dataset(Builder builder) { 252 mFieldIds = builder.mFieldIds; 253 mFieldValues = builder.mFieldValues; 254 mFieldPresentations = builder.mFieldPresentations; 255 mFieldDialogPresentations = builder.mFieldDialogPresentations; 256 mFieldInlinePresentations = builder.mFieldInlinePresentations; 257 mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations; 258 mFieldFilters = builder.mFieldFilters; 259 mFieldContent = builder.mFieldContent; 260 mPresentation = builder.mPresentation; 261 mDialogPresentation = builder.mDialogPresentation; 262 mInlinePresentation = builder.mInlinePresentation; 263 mInlineTooltipPresentation = builder.mInlineTooltipPresentation; 264 mAuthentication = builder.mAuthentication; 265 mId = builder.mId; 266 mAutofillDatatypes = builder.mAutofillDatatypes; 267 } 268 269 /** @hide */ 270 @TestApi 271 @SuppressLint({"ConcreteCollection", "NullableCollection"}) getAutofillDatatypes()272 public @Nullable ArrayList<String> getAutofillDatatypes() { 273 return mAutofillDatatypes; 274 } 275 276 /** @hide */ 277 @TestApi 278 @SuppressLint({"ConcreteCollection", "NullableCollection"}) getFieldIds()279 public @Nullable ArrayList<AutofillId> getFieldIds() { 280 return mFieldIds; 281 } 282 283 /** @hide */ 284 @TestApi 285 @SuppressLint({"ConcreteCollection", "NullableCollection"}) getFieldValues()286 public @Nullable ArrayList<AutofillValue> getFieldValues() { 287 return mFieldValues; 288 } 289 290 /** @hide */ 291 @TestApi getFieldPresentation(int index)292 public @Nullable RemoteViews getFieldPresentation(int index) { 293 final RemoteViews customPresentation = mFieldPresentations.get(index); 294 return customPresentation != null ? customPresentation : mPresentation; 295 } 296 297 /** @hide */ 298 @TestApi getFieldDialogPresentation(int index)299 public @Nullable RemoteViews getFieldDialogPresentation(int index) { 300 final RemoteViews customPresentation = mFieldDialogPresentations.get(index); 301 return customPresentation != null ? customPresentation : mDialogPresentation; 302 } 303 304 /** @hide */ 305 @TestApi getFieldInlinePresentation(int index)306 public @Nullable InlinePresentation getFieldInlinePresentation(int index) { 307 final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(index); 308 return inlinePresentation != null ? inlinePresentation : mInlinePresentation; 309 } 310 311 /** @hide */ 312 @TestApi getFieldInlineTooltipPresentation(int index)313 public @Nullable InlinePresentation getFieldInlineTooltipPresentation(int index) { 314 final InlinePresentation inlineTooltipPresentation = 315 mFieldInlineTooltipPresentations.get(index); 316 return inlineTooltipPresentation != null 317 ? inlineTooltipPresentation : mInlineTooltipPresentation; 318 } 319 320 /** @hide */ 321 @TestApi getFilter(int index)322 public @Nullable DatasetFieldFilter getFilter(int index) { 323 return mFieldFilters.get(index); 324 } 325 326 /** 327 * Returns the content to be filled for a non-text suggestion. This is only applicable to 328 * augmented autofill. The target field for the content is available via {@link #getFieldIds()} 329 * (guaranteed to have a single field id set when the return value here is non-null). See 330 * {@link Builder#setContent(AutofillId, ClipData)} for more info. 331 * 332 * @hide 333 */ 334 @TestApi getFieldContent()335 public @Nullable ClipData getFieldContent() { 336 return mFieldContent; 337 } 338 339 /** @hide */ 340 @TestApi getAuthentication()341 public @Nullable IntentSender getAuthentication() { 342 return mAuthentication; 343 } 344 345 /** @hide */ 346 @TestApi isEmpty()347 public boolean isEmpty() { 348 return mFieldIds == null || mFieldIds.isEmpty(); 349 } 350 351 @Override toString()352 public String toString() { 353 if (!sDebug) return super.toString(); 354 355 final StringBuilder builder = new StringBuilder("Dataset["); 356 if (mId == null) { 357 builder.append("noId"); 358 } else { 359 // Cannot disclose id because it could contain PII. 360 builder.append("id=").append(mId.length()).append("_chars"); 361 } 362 if (mFieldIds != null) { 363 builder.append(", fieldIds=").append(mFieldIds); 364 } 365 if (mFieldValues != null) { 366 builder.append(", fieldValues=").append(mFieldValues); 367 } 368 if (mFieldContent != null) { 369 builder.append(", fieldContent=").append(mFieldContent); 370 } 371 if (mFieldPresentations != null) { 372 builder.append(", fieldPresentations=").append(mFieldPresentations.size()); 373 } 374 if (mFieldDialogPresentations != null) { 375 builder.append(", fieldDialogPresentations=").append(mFieldDialogPresentations.size()); 376 } 377 if (mFieldInlinePresentations != null) { 378 builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size()); 379 } 380 if (mFieldInlineTooltipPresentations != null) { 381 builder.append(", fieldInlineTooltipInlinePresentations=").append( 382 mFieldInlineTooltipPresentations.size()); 383 } 384 if (mFieldFilters != null) { 385 builder.append(", fieldFilters=").append(mFieldFilters.size()); 386 } 387 if (mPresentation != null) { 388 builder.append(", hasPresentation"); 389 } 390 if (mDialogPresentation != null) { 391 builder.append(", hasDialogPresentation"); 392 } 393 if (mInlinePresentation != null) { 394 builder.append(", hasInlinePresentation"); 395 } 396 if (mInlineTooltipPresentation != null) { 397 builder.append(", hasInlineTooltipPresentation"); 398 } 399 if (mAuthentication != null) { 400 builder.append(", hasAuthentication"); 401 } 402 if (mAutofillDatatypes != null) { 403 builder.append(", autofillDatatypes=").append(mAutofillDatatypes); 404 } 405 return builder.append(']').toString(); 406 } 407 408 /** 409 * Gets the id of this dataset. 410 * 411 * @return The id of this dataset or {@code null} if not set 412 * 413 * @hide 414 */ 415 @TestApi getId()416 public @Nullable String getId() { 417 return mId; 418 } 419 420 /** 421 * Sets the reason as to why this dataset is eligible 422 * @hide 423 */ setEligibleReasonReason(@atasetEligibleReason int eligibleReason)424 public void setEligibleReasonReason(@DatasetEligibleReason int eligibleReason) { 425 this.mEligibleReason = eligibleReason; 426 } 427 428 /** 429 * Get the reason as to why this dataset is eligible. 430 * @hide 431 */ getEligibleReason()432 public @DatasetEligibleReason int getEligibleReason() { 433 return mEligibleReason; 434 } 435 436 /** 437 * A builder for {@link Dataset} objects. You must provide at least 438 * one value for a field or set an authentication intent. 439 */ 440 public static final class Builder { 441 private ArrayList<AutofillId> mFieldIds = new ArrayList<>(); 442 private ArrayList<AutofillValue> mFieldValues = new ArrayList(); 443 private ArrayList<RemoteViews> mFieldPresentations = new ArrayList(); 444 private ArrayList<RemoteViews> mFieldDialogPresentations = new ArrayList(); 445 private ArrayList<InlinePresentation> mFieldInlinePresentations = new ArrayList(); 446 private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations = new ArrayList(); 447 private ArrayList<DatasetFieldFilter> mFieldFilters = new ArrayList(); 448 private ArrayList<String> mAutofillDatatypes = new ArrayList(); 449 @Nullable private ClipData mFieldContent; 450 private RemoteViews mPresentation; 451 private RemoteViews mDialogPresentation; 452 @Nullable private InlinePresentation mInlinePresentation; 453 @Nullable private InlinePresentation mInlineTooltipPresentation; 454 private IntentSender mAuthentication; 455 private boolean mDestroyed; 456 @Nullable private String mId; 457 458 /** 459 * Usually, a field will be associated with a single autofill id and/or datatype. 460 * There could be null field value corresponding to different autofill ids or datatye 461 * values, but the implementation is ok with duplicating that information. 462 * This map is just for the purpose of optimization, to reduce the size of the pelled data 463 * over the binder transaction. 464 */ 465 private ArrayMap<Field, Integer> mFieldToIndexdMap = new ArrayMap<>(); 466 467 /** 468 * Creates a new builder. 469 * 470 * @param presentation The presentation used to visualize this dataset. 471 * @deprecated Use {@link #Builder(Presentations)} instead. 472 */ 473 @Deprecated Builder(@onNull RemoteViews presentation)474 public Builder(@NonNull RemoteViews presentation) { 475 Objects.requireNonNull(presentation, "presentation must be non-null"); 476 mPresentation = presentation; 477 } 478 479 /** 480 * Creates a new builder. 481 * 482 * <p>Only called by augmented autofill. 483 * 484 * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset 485 * as inline suggestions. If the dataset supports inline suggestions, 486 * this should not be null. 487 * @hide 488 * @deprecated Use {@link #Builder(Presentations)} instead. 489 */ 490 @SystemApi 491 @Deprecated Builder(@onNull InlinePresentation inlinePresentation)492 public Builder(@NonNull InlinePresentation inlinePresentation) { 493 Objects.requireNonNull(inlinePresentation, "inlinePresentation must be non-null"); 494 mInlinePresentation = inlinePresentation; 495 } 496 497 /** 498 * Creates a new builder. 499 * 500 * @param presentations The presentations used to visualize this dataset. 501 */ Builder(@onNull Presentations presentations)502 public Builder(@NonNull Presentations presentations) { 503 Objects.requireNonNull(presentations, "presentations must be non-null"); 504 505 mPresentation = presentations.getMenuPresentation(); 506 mInlinePresentation = presentations.getInlinePresentation(); 507 mInlineTooltipPresentation = presentations.getInlineTooltipPresentation(); 508 mDialogPresentation = presentations.getDialogPresentation(); 509 } 510 511 /** 512 * Creates a new builder for a dataset where each field will be visualized independently. 513 * 514 * <p>When using this constructor, a presentation must be provided for each field through 515 * {@link #setField(AutofillId, Field)}. 516 */ Builder()517 public Builder() { 518 } 519 520 /** 521 * Sets the {@link InlinePresentation} used to visualize this dataset as inline suggestions. 522 * If the dataset supports inline suggestions this should not be null. 523 * 524 * @throws IllegalStateException if {@link #build()} was already called. 525 * 526 * @return this builder. 527 * @deprecated Use {@link #Builder(Presentations)} instead. 528 */ 529 @Deprecated setInlinePresentation( @onNull InlinePresentation inlinePresentation)530 public @NonNull Builder setInlinePresentation( 531 @NonNull InlinePresentation inlinePresentation) { 532 throwIfDestroyed(); 533 Objects.requireNonNull(inlinePresentation, "inlinePresentation must be non-null"); 534 mInlinePresentation = inlinePresentation; 535 return this; 536 } 537 538 /** 539 * Visualizes this dataset as inline suggestions. 540 * 541 * @param inlinePresentation the {@link InlinePresentation} used to visualize this 542 * dataset as inline suggestions. If the dataset supports inline suggestions this 543 * should not be null. 544 * @param inlineTooltipPresentation the {@link InlinePresentation} used to show 545 * the tooltip for the {@code inlinePresentation}. 546 * 547 * @throws IllegalStateException if {@link #build()} was already called. 548 * 549 * @return this builder. 550 * @deprecated Use {@link #Builder(Presentations)} instead. 551 */ 552 @Deprecated setInlinePresentation( @onNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation)553 public @NonNull Builder setInlinePresentation( 554 @NonNull InlinePresentation inlinePresentation, 555 @NonNull InlinePresentation inlineTooltipPresentation) { 556 throwIfDestroyed(); 557 Objects.requireNonNull(inlinePresentation, "inlinePresentation must be non-null"); 558 Objects.requireNonNull(inlineTooltipPresentation, 559 "inlineTooltipPresentation must be non-null"); 560 mInlinePresentation = inlinePresentation; 561 mInlineTooltipPresentation = inlineTooltipPresentation; 562 return this; 563 } 564 565 /** 566 * Triggers a custom UI before before autofilling the screen with the contents of this 567 * dataset. 568 * 569 * <p><b>Note:</b> Although the name of this method suggests that it should be used just for 570 * authentication flow, it can be used for other advanced flows; see {@link AutofillService} 571 * for examples. 572 * 573 * <p>This method is called when you need to provide an authentication 574 * UI for the data set. For example, when a data set contains credit card information 575 * (such as number, expiration date, and verification code), you can display UI 576 * asking for the verification code before filing in the data. Even if the 577 * data set is completely populated the system will launch the specified authentication 578 * intent and will need your approval to fill it in. Since the data set is "locked" 579 * until the user authenticates it, typically this data set name is masked 580 * (for example, "VISA....1234"). Typically you would want to store the data set 581 * labels non-encrypted and the actual sensitive data encrypted and not in memory. 582 * This allows showing the labels in the UI while involving the user if one of 583 * the items with these labels is chosen. Note that if you use sensitive data as 584 * a label, for example an email address, then it should also be encrypted.</p> 585 * 586 * <p>When a user triggers autofill, the system launches the provided intent 587 * whose extras will have the {@link 588 * android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen content}, 589 * and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE client 590 * state}. Once you complete your authentication flow you should set the activity 591 * result to {@link android.app.Activity#RESULT_OK} and provide the fully populated 592 * {@link Dataset dataset} or a fully-populated {@link FillResponse response} by 593 * setting it to the {@link 594 * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you 595 * provide a dataset in the result, it will replace the authenticated dataset and 596 * will be immediately filled in. An exception to this behavior is if the original 597 * dataset represents a pinned inline suggestion (i.e. any of the field in the dataset 598 * has a pinned inline presentation, see {@link InlinePresentation#isPinned()}), then 599 * the original dataset will not be replaced, 600 * so that it can be triggered as a pending intent again. 601 * If you provide a response, it will replace the 602 * current response and the UI will be refreshed. For example, if you provided 603 * credit card information without the CVV for the data set in the {@link FillResponse 604 * response} then the returned data set should contain the CVV entry. 605 * 606 * <p><b>Note:</b> Do not make the provided pending intent 607 * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the 608 * platform needs to fill in the authentication arguments. 609 * 610 * @param authentication Intent to an activity with your authentication flow. 611 * 612 * @throws IllegalStateException if {@link #build()} was already called. 613 * 614 * @return this builder. 615 * 616 * @see android.app.PendingIntent 617 */ setAuthentication(@ullable IntentSender authentication)618 public @NonNull Builder setAuthentication(@Nullable IntentSender authentication) { 619 throwIfDestroyed(); 620 mAuthentication = authentication; 621 return this; 622 } 623 624 /** 625 * Sets the id for the dataset so its usage can be tracked. 626 * 627 * <p>Dataset usage can be tracked for 2 purposes: 628 * 629 * <ul> 630 * <li>For statistical purposes, the service can call 631 * {@link AutofillService#getFillEventHistory()} when handling {@link 632 * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)} 633 * calls. 634 * <li>For normal autofill workflow, the service can call 635 * {@link SaveRequest#getDatasetIds()} when handling 636 * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} calls. 637 * </ul> 638 * 639 * @param id id for this dataset or {@code null} to unset. 640 * 641 * @throws IllegalStateException if {@link #build()} was already called. 642 * 643 * @return this builder. 644 */ setId(@ullable String id)645 public @NonNull Builder setId(@Nullable String id) { 646 throwIfDestroyed(); 647 mId = id; 648 return this; 649 } 650 651 /** 652 * Sets the content for a field. 653 * 654 * <p>Only called by augmented autofill. 655 * 656 * <p>For a given field, either a {@link AutofillValue value} or content can be filled, but 657 * not both. Furthermore, when filling content, only a single field can be filled. 658 * 659 * <p>The provided {@link ClipData} can contain content URIs (e.g. a URI for an image). 660 * The augmented autofill provider setting the content here must itself have at least 661 * read permissions to any passed content URIs. If the user accepts the suggestion backed 662 * by the content URI(s), the platform will automatically grant read URI permissions to 663 * the app being autofilled, just before passing the content URI(s) to it. The granted 664 * permissions will be transient and tied to the lifecycle of the activity being filled 665 * (when the activity finishes, permissions will automatically be revoked by the platform). 666 * 667 * @param id id returned by 668 * {@link android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 669 * @param content content to be autofilled. Pass {@code null} if you do not have the content 670 * but the target view is a logical part of the dataset. For example, if the dataset needs 671 * authentication. 672 * 673 * @throws IllegalStateException if {@link #build()} was already called. 674 * @throws IllegalArgumentException if the provided content 675 * {@link ClipData.Item#getIntent() contains an intent} 676 * 677 * @return this builder. 678 * 679 * @hide 680 */ 681 @TestApi 682 @SystemApi 683 @SuppressLint("MissingGetterMatchingBuilder") setContent(@onNull AutofillId id, @Nullable ClipData content)684 public @NonNull Builder setContent(@NonNull AutofillId id, @Nullable ClipData content) { 685 throwIfDestroyed(); 686 if (content != null) { 687 for (int i = 0; i < content.getItemCount(); i++) { 688 Preconditions.checkArgument(content.getItemAt(i).getIntent() == null, 689 "Content items cannot contain an Intent: content=" + content); 690 } 691 } 692 setLifeTheUniverseAndEverything(id, null, null, null, null, null, null); 693 mFieldContent = content; 694 return this; 695 } 696 697 /** 698 * Sets the value of a field. 699 * 700 * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would 701 * throw an {@link IllegalStateException} if this builder was constructed without a 702 * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and 703 * higher removed this restriction because datasets used as an 704 * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT 705 * authentication result} do not need a presentation. But if you don't set the presentation 706 * in the constructor in a dataset that is meant to be shown to the user, the autofill UI 707 * for this field will not be displayed. 708 * 709 * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and 710 * higher, datasets that require authentication can be also be filtered by passing a 711 * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter. 712 * 713 * @param id id returned by {@link 714 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 715 * @param value value to be autofilled. Pass {@code null} if you do not have the value 716 * but the target view is a logical part of the dataset. For example, if 717 * the dataset needs authentication and you have no access to the value. 718 * 719 * @throws IllegalStateException if {@link #build()} was already called. 720 * 721 * @return this builder. 722 * @deprecated Use {@link #setField(AutofillId, Field)} instead. 723 */ 724 @Deprecated setValue(@onNull AutofillId id, @Nullable AutofillValue value)725 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) { 726 throwIfDestroyed(); 727 setLifeTheUniverseAndEverything(id, value, null, null, null, null, null); 728 return this; 729 } 730 731 /** 732 * Sets the value of a field, using a custom {@link RemoteViews presentation} to 733 * visualize it. 734 * 735 * <p><b>Note:</b> On Android {@link android.os.Build.VERSION_CODES#P} and 736 * higher, datasets that require authentication can be also be filtered by passing a 737 * {@link AutofillValue#forText(CharSequence) text value} as the {@code value} parameter. 738 * 739 * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color 740 * or background color: Autofill on different platforms may have different themes. 741 * 742 * @param id id returned by {@link 743 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 744 * @param value the value to be autofilled. Pass {@code null} if you do not have the value 745 * but the target view is a logical part of the dataset. For example, if 746 * the dataset needs authentication and you have no access to the value. 747 * @param presentation the presentation used to visualize this field. 748 * 749 * @throws IllegalStateException if {@link #build()} was already called. 750 * 751 * @return this builder. 752 * @deprecated Use {@link #setField(AutofillId, Field)} instead. 753 */ 754 @Deprecated setValue(@onNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation)755 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, 756 @NonNull RemoteViews presentation) { 757 throwIfDestroyed(); 758 Objects.requireNonNull(presentation, "presentation cannot be null"); 759 setLifeTheUniverseAndEverything(id, value, presentation, null, null, null, null); 760 return this; 761 } 762 763 /** 764 * Sets the value of a field using an <a href="#Filtering">explicit filter</a>. 765 * 766 * <p>This method is typically used when the dataset requires authentication and the service 767 * does not know its value but wants to hide the dataset after the user enters a minimum 768 * number of characters. For example, if the dataset represents a credit card number and the 769 * service does not want to show the "Tap to authenticate" message until the user tapped 770 * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}. 771 * 772 * <p><b>Note:</b> If the dataset requires authentication but the service knows its text 773 * value it's easier to filter by calling {@link #setValue(AutofillId, AutofillValue)} and 774 * use the value to filter. 775 * 776 * @param id id returned by {@link 777 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 778 * @param value the value to be autofilled. Pass {@code null} if you do not have the value 779 * but the target view is a logical part of the dataset. For example, if 780 * the dataset needs authentication and you have no access to the value. 781 * @param filter regex used to determine if the dataset should be shown in the autofill UI; 782 * when {@code null}, it disables filtering on that dataset (this is the recommended 783 * approach when {@code value} is not {@code null} and field contains sensitive data 784 * such as passwords). 785 * 786 * @return this builder. 787 * @throws IllegalStateException if the builder was constructed without a 788 * {@link RemoteViews presentation} or {@link #build()} was already called. 789 * @deprecated Use {@link #setField(AutofillId, Field)} instead. 790 */ 791 @Deprecated setValue(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter)792 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, 793 @Nullable Pattern filter) { 794 throwIfDestroyed(); 795 Preconditions.checkState(mPresentation != null, 796 "Dataset presentation not set on constructor"); 797 setLifeTheUniverseAndEverything( 798 id, value, null, null, null, new DatasetFieldFilter(filter), null); 799 return this; 800 } 801 802 /** 803 * Sets the value of a field, using a custom {@link RemoteViews presentation} to 804 * visualize it and a <a href="#Filtering">explicit filter</a>. 805 * 806 * <p>This method is typically used when the dataset requires authentication and the service 807 * does not know its value but wants to hide the dataset after the user enters a minimum 808 * number of characters. For example, if the dataset represents a credit card number and the 809 * service does not want to show the "Tap to authenticate" message until the user tapped 810 * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}. 811 * 812 * <p><b>Note:</b> If the dataset requires authentication but the service knows its text 813 * value it's easier to filter by calling 814 * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter. 815 * 816 * @param id id returned by {@link 817 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 818 * @param value the value to be autofilled. Pass {@code null} if you do not have the value 819 * but the target view is a logical part of the dataset. For example, if 820 * the dataset needs authentication and you have no access to the value. 821 * @param filter regex used to determine if the dataset should be shown in the autofill UI; 822 * when {@code null}, it disables filtering on that dataset (this is the recommended 823 * approach when {@code value} is not {@code null} and field contains sensitive data 824 * such as passwords). 825 * @param presentation the presentation used to visualize this field. 826 * 827 * @throws IllegalStateException if {@link #build()} was already called. 828 * 829 * @return this builder. 830 * @deprecated Use {@link #setField(AutofillId, Field)} instead. 831 */ 832 @Deprecated setValue(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation)833 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, 834 @Nullable Pattern filter, @NonNull RemoteViews presentation) { 835 throwIfDestroyed(); 836 Objects.requireNonNull(presentation, "presentation cannot be null"); 837 setLifeTheUniverseAndEverything(id, value, presentation, null, null, 838 new DatasetFieldFilter(filter), null); 839 return this; 840 } 841 842 /** 843 * Sets the value of a field, using a custom {@link RemoteViews presentation} to 844 * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion. 845 * 846 * <p><b>Note:</b> If the dataset requires authentication but the service knows its text 847 * value it's easier to filter by calling 848 * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter. 849 * 850 * @param id id returned by {@link 851 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 852 * @param value the value to be autofilled. Pass {@code null} if you do not have the value 853 * but the target view is a logical part of the dataset. For example, if 854 * the dataset needs authentication and you have no access to the value. 855 * @param presentation the presentation used to visualize this field. 856 * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset 857 * as inline suggestions. If the dataset supports inline suggestions, 858 * this should not be null. 859 * 860 * @throws IllegalStateException if {@link #build()} was already called. 861 * 862 * @return this builder. 863 * @deprecated Use {@link #setField(AutofillId, Field)} instead. 864 */ 865 @Deprecated setValue(@onNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation)866 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, 867 @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) { 868 throwIfDestroyed(); 869 Objects.requireNonNull(presentation, "presentation cannot be null"); 870 Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null"); 871 setLifeTheUniverseAndEverything( 872 id, value, presentation, inlinePresentation, null, null, null); 873 return this; 874 } 875 876 /** 877 * Sets the value of a field, using a custom {@link RemoteViews presentation} to 878 * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion. 879 * 880 * @see #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation) 881 * 882 * @param id id returned by {@link 883 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 884 * @param value the value to be autofilled. Pass {@code null} if you do not have the value 885 * but the target view is a logical part of the dataset. For example, if 886 * the dataset needs authentication and you have no access to the value. 887 * @param presentation the presentation used to visualize this field. 888 * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset 889 * as inline suggestions. If the dataset supports inline suggestions, 890 * this should not be null. 891 * @param inlineTooltipPresentation The {@link InlinePresentation} used to show 892 * the tooltip for the {@code inlinePresentation}. 893 * 894 * @throws IllegalStateException if {@link #build()} was already called. 895 * 896 * @return this builder. 897 * @deprecated Use {@link #setField(AutofillId, Field)} instead. 898 */ 899 @Deprecated setValue(@onNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation)900 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, 901 @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, 902 @NonNull InlinePresentation inlineTooltipPresentation) { 903 throwIfDestroyed(); 904 Objects.requireNonNull(presentation, "presentation cannot be null"); 905 Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null"); 906 Objects.requireNonNull(inlineTooltipPresentation, 907 "inlineTooltipPresentation cannot be null"); 908 setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, 909 inlineTooltipPresentation, null, null); 910 return this; 911 } 912 913 /** 914 * Sets the value of a field, using a custom {@link RemoteViews presentation} to 915 * visualize it and a <a href="#Filtering">explicit filter</a>, and an 916 * {@link InlinePresentation} to visualize it as an inline suggestion. 917 * 918 * <p>This method is typically used when the dataset requires authentication and the service 919 * does not know its value but wants to hide the dataset after the user enters a minimum 920 * number of characters. For example, if the dataset represents a credit card number and the 921 * service does not want to show the "Tap to authenticate" message until the user tapped 922 * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}. 923 * 924 * <p><b>Note:</b> If the dataset requires authentication but the service knows its text 925 * value it's easier to filter by calling 926 * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter. 927 * 928 * @param id id returned by {@link 929 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 930 * @param value the value to be autofilled. Pass {@code null} if you do not have the value 931 * but the target view is a logical part of the dataset. For example, if 932 * the dataset needs authentication and you have no access to the value. 933 * @param filter regex used to determine if the dataset should be shown in the autofill UI; 934 * when {@code null}, it disables filtering on that dataset (this is the recommended 935 * approach when {@code value} is not {@code null} and field contains sensitive data 936 * such as passwords). 937 * @param presentation the presentation used to visualize this field. 938 * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset 939 * as inline suggestions. If the dataset supports inline suggestions, this 940 * should not be null. 941 * 942 * @throws IllegalStateException if {@link #build()} was already called. 943 * 944 * @return this builder. 945 * @deprecated Use {@link #setField(AutofillId, Field)} instead. 946 */ 947 @Deprecated setValue(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation)948 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, 949 @Nullable Pattern filter, @NonNull RemoteViews presentation, 950 @NonNull InlinePresentation inlinePresentation) { 951 throwIfDestroyed(); 952 Objects.requireNonNull(presentation, "presentation cannot be null"); 953 Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null"); 954 setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null, 955 new DatasetFieldFilter(filter), null); 956 return this; 957 } 958 959 /** 960 * Sets the value of a field, using a custom {@link RemoteViews presentation} to 961 * visualize it and a <a href="#Filtering">explicit filter</a>, and an 962 * {@link InlinePresentation} to visualize it as an inline suggestion. 963 * 964 * @see #setValue(AutofillId, AutofillValue, Pattern, RemoteViews, InlinePresentation) 965 * 966 * @param id id returned by {@link 967 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 968 * @param value the value to be autofilled. Pass {@code null} if you do not have the value 969 * but the target view is a logical part of the dataset. For example, if 970 * the dataset needs authentication and you have no access to the value. 971 * @param filter regex used to determine if the dataset should be shown in the autofill UI; 972 * when {@code null}, it disables filtering on that dataset (this is the recommended 973 * approach when {@code value} is not {@code null} and field contains sensitive data 974 * such as passwords). 975 * @param presentation the presentation used to visualize this field. 976 * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset 977 * as inline suggestions. If the dataset supports inline suggestions, this 978 * should not be null. 979 * @param inlineTooltipPresentation The {@link InlinePresentation} used to show 980 * the tooltip for the {@code inlinePresentation}. 981 * 982 * @throws IllegalStateException if {@link #build()} was already called. 983 * 984 * @return this builder. 985 * @deprecated Use {@link #setField(AutofillId, Field)} instead. 986 */ 987 @Deprecated setValue(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation)988 public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, 989 @Nullable Pattern filter, @NonNull RemoteViews presentation, 990 @NonNull InlinePresentation inlinePresentation, 991 @NonNull InlinePresentation inlineTooltipPresentation) { 992 throwIfDestroyed(); 993 Objects.requireNonNull(presentation, "presentation cannot be null"); 994 Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null"); 995 Objects.requireNonNull(inlineTooltipPresentation, 996 "inlineTooltipPresentation cannot be null"); 997 setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, 998 inlineTooltipPresentation, new DatasetFieldFilter(filter), null); 999 return this; 1000 } 1001 1002 /** 1003 * Sets the value of a field. 1004 * 1005 * Before Android 13, this information could be provided using several overloaded 1006 * setValue(...) methods. This method replaces those with a Builder pattern. 1007 * For example, in the old workflow, the app sets a field would be: 1008 * <pre class="prettyprint"> 1009 * Dataset.Builder dataset = new Dataset.Builder(); 1010 * if (filter != null) { 1011 * if (presentation != null) { 1012 * if (inlinePresentation != null) { 1013 * dataset.setValue(id, value, filter, presentation, inlinePresentation) 1014 * } else { 1015 * dataset.setValue(id, value, filter, presentation); 1016 * } 1017 * } else { 1018 * dataset.setValue(id, value, filter); 1019 * } 1020 * } else { 1021 * if (presentation != null) { 1022 * if (inlinePresentation != null) { 1023 * dataset.setValue(id, value, presentation, inlinePresentation) 1024 * } else { 1025 * dataset.setValue(id, value, presentation); 1026 * } 1027 * } else { 1028 * dataset.setValue(id, value); 1029 * } 1030 * } 1031 * </pre> 1032 * <p>The new workflow would be: 1033 * <pre class="prettyprint"> 1034 * Field.Builder fieldBuilder = new Field.Builder(); 1035 * if (value != null) { 1036 * fieldBuilder.setValue(value); 1037 * } 1038 * if (filter != null) { 1039 * fieldBuilder.setFilter(filter); 1040 * } 1041 * Presentations.Builder presentationsBuilder = new Presentations.Builder(); 1042 * if (presentation != null) { 1043 * presentationsBuilder.setMenuPresentation(presentation); 1044 * } 1045 * if (inlinePresentation != null) { 1046 * presentationsBuilder.setInlinePresentation(inlinePresentation); 1047 * } 1048 * if (dialogPresentation != null) { 1049 * presentationsBuilder.setDialogPresentation(dialogPresentation); 1050 * } 1051 * fieldBuilder.setPresentations(presentationsBuilder.build()); 1052 * dataset.setField(id, fieldBuilder.build()); 1053 * </pre> 1054 * 1055 * @see Field 1056 * 1057 * @param id id returned by {@link 1058 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 1059 * @param field the fill information about the field. 1060 * 1061 * @throws IllegalStateException if {@link #build()} was already called. 1062 * 1063 * @return this builder. 1064 */ setField(@onNull AutofillId id, @Nullable Field field)1065 public @NonNull Builder setField(@NonNull AutofillId id, @Nullable Field field) { 1066 throwIfDestroyed(); 1067 1068 if (mFieldToIndexdMap.containsKey(field)) { 1069 int index = mFieldToIndexdMap.get(field); 1070 if (mFieldIds.get(index) == null) { 1071 mFieldIds.set(index, id); 1072 return this; 1073 } 1074 // if the Autofill Id is already set, ignore and proceed as if setting in a new 1075 // value. 1076 } 1077 int index; 1078 if (field == null) { 1079 index = setLifeTheUniverseAndEverything(id, null, null, null, null, null, null); 1080 } else { 1081 final DatasetFieldFilter filter = field.getDatasetFieldFilter(); 1082 final Presentations presentations = field.getPresentations(); 1083 if (presentations == null) { 1084 index = setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null, 1085 filter, null); 1086 } else { 1087 index = setLifeTheUniverseAndEverything(id, field.getValue(), 1088 presentations.getMenuPresentation(), 1089 presentations.getInlinePresentation(), 1090 presentations.getInlineTooltipPresentation(), filter, 1091 presentations.getDialogPresentation()); 1092 } 1093 } 1094 mFieldToIndexdMap.put(field, index); 1095 return this; 1096 } 1097 1098 /** 1099 * Adds a field to this Dataset with a specific type. This is used to send back Field 1100 * information when Autofilling with platform detections is on. 1101 * Platform detections are on when receiving a populated list from 1102 * FillRequest#getHints(). 1103 * 1104 * Populate every field/type known for this user for this app. 1105 * 1106 * For example, if getHints() contains "username" and "password", 1107 * a new Dataset should be created that calls this method twice, 1108 * one for the username, then another for the password (assuming 1109 * the only one credential pair is found for the user). If a user 1110 * has two credential pairs, then two Datasets should be created, 1111 * and so on. 1112 * 1113 * @param hint An autofill hint returned from {@link 1114 * FillRequest#getHints()}. 1115 * 1116 * @param field the fill information about the field. 1117 * 1118 * @throws IllegalStateException if {@link #build()} was already called 1119 * or this builder also contains AutofillId information 1120 * 1121 * @return this builder. 1122 */ setField(@onNull String hint, @NonNull Field field)1123 public @NonNull Dataset.Builder setField(@NonNull String hint, @NonNull Field field) { 1124 throwIfDestroyed(); 1125 1126 if (mFieldToIndexdMap.containsKey(field)) { 1127 int index = mFieldToIndexdMap.get(field); 1128 if (mAutofillDatatypes.get(index) == null) { 1129 mAutofillDatatypes.set(index, hint); 1130 return this; 1131 } 1132 // if the hint is already set, ignore and proceed as if setting in a new hint. 1133 } 1134 1135 int index; 1136 final DatasetFieldFilter filter = field.getDatasetFieldFilter(); 1137 final Presentations presentations = field.getPresentations(); 1138 if (presentations == null) { 1139 index = setLifeTheUniverseAndEverything(hint, field.getValue(), null, null, null, 1140 filter, null); 1141 } else { 1142 index = setLifeTheUniverseAndEverything(hint, field.getValue(), 1143 presentations.getMenuPresentation(), 1144 presentations.getInlinePresentation(), 1145 presentations.getInlineTooltipPresentation(), filter, 1146 presentations.getDialogPresentation()); 1147 } 1148 mFieldToIndexdMap.put(field, index); 1149 return this; 1150 } 1151 1152 /** 1153 * Adds a field to this Dataset that is relevant to all applicable hints. This is used to 1154 * provide field information when autofill with platform detections is enabled. 1155 * Platform detections are on when receiving a populated list from 1156 * FillRequest#getHints(). 1157 * 1158 * @param field the fill information about the field. 1159 * 1160 * @throws IllegalStateException if {@link #build()} was already called 1161 * or this builder also contains AutofillId information 1162 * 1163 * @return this builder. 1164 */ setFieldForAllHints(@onNull Field field)1165 public @NonNull Dataset.Builder setFieldForAllHints(@NonNull Field field) { 1166 return setField(AutofillManager.ANY_HINT, field); 1167 } 1168 1169 /** 1170 * Sets the value of a field with an <a href="#Filtering">explicit filter</a>, and using an 1171 * {@link InlinePresentation} to visualize it as an inline suggestion. 1172 * 1173 * <p>Only called by augmented autofill. 1174 * 1175 * @param id id returned by {@link 1176 * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. 1177 * @param value the value to be autofilled. Pass {@code null} if you do not have the value 1178 * but the target view is a logical part of the dataset. For example, if 1179 * the dataset needs authentication and you have no access to the value. 1180 * @param filter regex used to determine if the dataset should be shown in the autofill UI; 1181 * when {@code null}, it disables filtering on that dataset (this is the recommended 1182 * approach when {@code value} is not {@code null} and field contains sensitive data 1183 * such as passwords). 1184 * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset 1185 * as inline suggestions. If the dataset supports inline suggestions, this 1186 * should not be null. 1187 * 1188 * @throws IllegalStateException if {@link #build()} was already called. 1189 * 1190 * @return this builder. 1191 * @deprecated Use {@link #setField(AutofillId, Field)} instead. 1192 * @hide 1193 */ 1194 @Deprecated 1195 @SystemApi setFieldInlinePresentation(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull InlinePresentation inlinePresentation)1196 public @NonNull Builder setFieldInlinePresentation(@NonNull AutofillId id, 1197 @Nullable AutofillValue value, @Nullable Pattern filter, 1198 @NonNull InlinePresentation inlinePresentation) { 1199 throwIfDestroyed(); 1200 Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null"); 1201 setLifeTheUniverseAndEverything(id, value, null, inlinePresentation, null, 1202 new DatasetFieldFilter(filter), null); 1203 return this; 1204 } 1205 1206 /** Returns the index at which this id was modified or inserted */ setLifeTheUniverseAndEverything(@onNull String datatype, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation tooltip, @Nullable DatasetFieldFilter filter, @Nullable RemoteViews dialogPresentation)1207 private int setLifeTheUniverseAndEverything(@NonNull String datatype, 1208 @Nullable AutofillValue value, 1209 @Nullable RemoteViews presentation, 1210 @Nullable InlinePresentation inlinePresentation, 1211 @Nullable InlinePresentation tooltip, 1212 @Nullable DatasetFieldFilter filter, 1213 @Nullable RemoteViews dialogPresentation) { 1214 Objects.requireNonNull(datatype, "datatype cannot be null"); 1215 final int existingIdx = mAutofillDatatypes.indexOf(datatype); 1216 if (existingIdx >= 0) { 1217 mAutofillDatatypes.add(datatype); 1218 mFieldValues.set(existingIdx, value); 1219 mFieldPresentations.set(existingIdx, presentation); 1220 mFieldDialogPresentations.set(existingIdx, dialogPresentation); 1221 mFieldInlinePresentations.set(existingIdx, inlinePresentation); 1222 mFieldInlineTooltipPresentations.set(existingIdx, tooltip); 1223 mFieldFilters.set(existingIdx, filter); 1224 return existingIdx; 1225 } 1226 mFieldIds.add(null); 1227 mAutofillDatatypes.add(datatype); 1228 mFieldValues.add(value); 1229 mFieldPresentations.add(presentation); 1230 mFieldDialogPresentations.add(dialogPresentation); 1231 mFieldInlinePresentations.add(inlinePresentation); 1232 mFieldInlineTooltipPresentations.add(tooltip); 1233 mFieldFilters.add(filter); 1234 return mFieldIds.size() - 1; 1235 } 1236 1237 /** Returns the index at which this id was modified or inserted */ setLifeTheUniverseAndEverything(@onNull AutofillId id, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation tooltip, @Nullable DatasetFieldFilter filter, @Nullable RemoteViews dialogPresentation)1238 private int setLifeTheUniverseAndEverything(@NonNull AutofillId id, 1239 @Nullable AutofillValue value, @Nullable RemoteViews presentation, 1240 @Nullable InlinePresentation inlinePresentation, 1241 @Nullable InlinePresentation tooltip, 1242 @Nullable DatasetFieldFilter filter, 1243 @Nullable RemoteViews dialogPresentation) { 1244 Objects.requireNonNull(id, "id cannot be null"); 1245 final int existingIdx = mFieldIds.indexOf(id); 1246 if (existingIdx >= 0) { 1247 mFieldValues.set(existingIdx, value); 1248 mFieldPresentations.set(existingIdx, presentation); 1249 mFieldDialogPresentations.set(existingIdx, dialogPresentation); 1250 mFieldInlinePresentations.set(existingIdx, inlinePresentation); 1251 mFieldInlineTooltipPresentations.set(existingIdx, tooltip); 1252 mFieldFilters.set(existingIdx, filter); 1253 return existingIdx; 1254 } 1255 mFieldIds.add(id); 1256 mAutofillDatatypes.add(null); 1257 mFieldValues.add(value); 1258 mFieldPresentations.add(presentation); 1259 mFieldDialogPresentations.add(dialogPresentation); 1260 mFieldInlinePresentations.add(inlinePresentation); 1261 mFieldInlineTooltipPresentations.add(tooltip); 1262 mFieldFilters.add(filter); 1263 return mFieldIds.size() - 1; 1264 } 1265 createFromParcel( @ullable AutofillId id, @Nullable String datatype, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation tooltip, @Nullable DatasetFieldFilter filter, @Nullable RemoteViews dialogPresentation)1266 private void createFromParcel( 1267 @Nullable AutofillId id, @Nullable String datatype, 1268 @Nullable AutofillValue value, @Nullable RemoteViews presentation, 1269 @Nullable InlinePresentation inlinePresentation, 1270 @Nullable InlinePresentation tooltip, 1271 @Nullable DatasetFieldFilter filter, 1272 @Nullable RemoteViews dialogPresentation) { 1273 if (id != null) { 1274 final int existingIdx = mFieldIds.indexOf(id); 1275 if (existingIdx >= 0) { 1276 mFieldValues.set(existingIdx, value); 1277 mFieldPresentations.set(existingIdx, presentation); 1278 mFieldDialogPresentations.set(existingIdx, dialogPresentation); 1279 mFieldInlinePresentations.set(existingIdx, inlinePresentation); 1280 mFieldInlineTooltipPresentations.set(existingIdx, tooltip); 1281 mFieldFilters.set(existingIdx, filter); 1282 return; 1283 } 1284 } 1285 mFieldIds.add(id); 1286 mAutofillDatatypes.add(datatype); 1287 mFieldValues.add(value); 1288 mFieldPresentations.add(presentation); 1289 mFieldDialogPresentations.add(dialogPresentation); 1290 mFieldInlinePresentations.add(inlinePresentation); 1291 mFieldInlineTooltipPresentations.add(tooltip); 1292 mFieldFilters.add(filter); 1293 return; 1294 } 1295 1296 /** 1297 * Creates a new {@link Dataset} instance. 1298 * 1299 * <p>You should not interact with this builder once this method is called. 1300 * 1301 * @throws IllegalStateException if no field was set (through 1302 * {@link #setField(AutofillId, Field)}), or if {@link #build()} was already called. 1303 * 1304 * @return The built dataset. 1305 */ build()1306 public @NonNull Dataset build() { 1307 throwIfDestroyed(); 1308 mDestroyed = true; 1309 if (mFieldIds == null && mAutofillDatatypes == null) { 1310 throw new IllegalStateException("at least one of field or datatype must be set"); 1311 } 1312 if (mFieldIds != null && mAutofillDatatypes != null) { 1313 if (mFieldIds.size() == 0 && mAutofillDatatypes.size() == 0) { 1314 throw new IllegalStateException( 1315 "at least one of field or datatype must be set"); 1316 } 1317 } 1318 if (mFieldContent != null) { 1319 if (mFieldIds.size() > 1) { 1320 throw new IllegalStateException( 1321 "when filling content, only one field can be filled"); 1322 } 1323 if (mFieldValues.get(0) != null) { 1324 throw new IllegalStateException("cannot fill both content and values"); 1325 } 1326 } 1327 return new Dataset(this); 1328 } 1329 throwIfDestroyed()1330 private void throwIfDestroyed() { 1331 if (mDestroyed) { 1332 throw new IllegalStateException("Already called #build()"); 1333 } 1334 } 1335 } 1336 1337 ///////////////////////////////////// 1338 // Parcelable "contract" methods. // 1339 ///////////////////////////////////// 1340 1341 @Override describeContents()1342 public int describeContents() { 1343 return 0; 1344 } 1345 1346 @Override writeToParcel(Parcel parcel, int flags)1347 public void writeToParcel(Parcel parcel, int flags) { 1348 parcel.writeParcelable(mPresentation, flags); 1349 parcel.writeParcelable(mDialogPresentation, flags); 1350 parcel.writeParcelable(mInlinePresentation, flags); 1351 parcel.writeParcelable(mInlineTooltipPresentation, flags); 1352 parcel.writeTypedList(mFieldIds, flags); 1353 parcel.writeTypedList(mFieldValues, flags); 1354 parcel.writeTypedList(mFieldPresentations, flags); 1355 parcel.writeTypedList(mFieldDialogPresentations, flags); 1356 parcel.writeTypedList(mFieldInlinePresentations, flags); 1357 parcel.writeTypedList(mFieldInlineTooltipPresentations, flags); 1358 parcel.writeTypedList(mFieldFilters, flags); 1359 parcel.writeStringList(mAutofillDatatypes); 1360 parcel.writeParcelable(mFieldContent, flags); 1361 parcel.writeParcelable(mAuthentication, flags); 1362 parcel.writeString(mId); 1363 parcel.writeInt(mEligibleReason); 1364 } 1365 1366 public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() { 1367 @Override 1368 public Dataset createFromParcel(Parcel parcel) { 1369 final RemoteViews presentation = parcel.readParcelable(null, 1370 android.widget.RemoteViews.class); 1371 final RemoteViews dialogPresentation = parcel.readParcelable(null, 1372 android.widget.RemoteViews.class); 1373 final InlinePresentation inlinePresentation = parcel.readParcelable(null, 1374 android.service.autofill.InlinePresentation.class); 1375 final InlinePresentation inlineTooltipPresentation = 1376 parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); 1377 final ArrayList<AutofillId> ids = 1378 parcel.createTypedArrayList(AutofillId.CREATOR); 1379 final ArrayList<AutofillValue> values = 1380 parcel.createTypedArrayList(AutofillValue.CREATOR); 1381 final ArrayList<RemoteViews> presentations = 1382 parcel.createTypedArrayList(RemoteViews.CREATOR); 1383 final ArrayList<RemoteViews> dialogPresentations = 1384 parcel.createTypedArrayList(RemoteViews.CREATOR); 1385 final ArrayList<InlinePresentation> inlinePresentations = 1386 parcel.createTypedArrayList(InlinePresentation.CREATOR); 1387 final ArrayList<InlinePresentation> inlineTooltipPresentations = 1388 parcel.createTypedArrayList(InlinePresentation.CREATOR); 1389 final ArrayList<DatasetFieldFilter> filters = 1390 parcel.createTypedArrayList(DatasetFieldFilter.CREATOR); 1391 final ArrayList<String> autofillDatatypes = 1392 parcel.createStringArrayList(); 1393 final ClipData fieldContent = parcel.readParcelable(null, 1394 android.content.ClipData.class); 1395 final IntentSender authentication = parcel.readParcelable(null, 1396 android.content.IntentSender.class); 1397 final String datasetId = parcel.readString(); 1398 final int eligibleReason = parcel.readInt(); 1399 1400 // Always go through the builder to ensure the data ingested by 1401 // the system obeys the contract of the builder to avoid attacks 1402 // using specially crafted parcels. 1403 final Builder builder; 1404 if (presentation != null || inlinePresentation != null || dialogPresentation != null) { 1405 final Presentations.Builder presentationsBuilder = new Presentations.Builder(); 1406 if (presentation != null) { 1407 presentationsBuilder.setMenuPresentation(presentation); 1408 } 1409 if (inlinePresentation != null) { 1410 presentationsBuilder.setInlinePresentation(inlinePresentation); 1411 } 1412 if (inlineTooltipPresentation != null) { 1413 presentationsBuilder.setInlineTooltipPresentation(inlineTooltipPresentation); 1414 } 1415 if (dialogPresentation != null) { 1416 presentationsBuilder.setDialogPresentation(dialogPresentation); 1417 } 1418 builder = new Builder(presentationsBuilder.build()); 1419 } else { 1420 builder = new Builder(); 1421 } 1422 1423 if (fieldContent != null) { 1424 builder.setContent(ids.get(0), fieldContent); 1425 } 1426 final int inlinePresentationsSize = inlinePresentations.size(); 1427 for (int i = 0; i < ids.size(); i++) { 1428 final AutofillId id = ids.get(i); 1429 final String datatype = autofillDatatypes.get(i); 1430 final AutofillValue value = values.get(i); 1431 final RemoteViews fieldPresentation = presentations.get(i); 1432 final RemoteViews fieldDialogPresentation = dialogPresentations.get(i); 1433 final InlinePresentation fieldInlinePresentation = 1434 i < inlinePresentationsSize ? inlinePresentations.get(i) : null; 1435 final InlinePresentation fieldInlineTooltipPresentation = 1436 i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null; 1437 final DatasetFieldFilter filter = filters.get(i); 1438 builder.createFromParcel(id, datatype, value, fieldPresentation, 1439 fieldInlinePresentation, fieldInlineTooltipPresentation, filter, 1440 fieldDialogPresentation); 1441 } 1442 builder.setAuthentication(authentication); 1443 builder.setId(datasetId); 1444 Dataset dataset = builder.build(); 1445 dataset.mEligibleReason = eligibleReason; 1446 return dataset; 1447 } 1448 1449 @Override 1450 public Dataset[] newArray(int size) { 1451 return new Dataset[size]; 1452 } 1453 }; 1454 1455 /** 1456 * Helper class used to indicate when the service explicitly set a {@link Pattern} filter for a 1457 * dataset field‐ we cannot use a {@link Pattern} directly because then we wouldn't be 1458 * able to differentiate whether the service explicitly passed a {@code null} filter to disable 1459 * filter, or when it called the methods that does not take a filter {@link Pattern}. 1460 * 1461 * @hide 1462 */ 1463 @TestApi 1464 public static final class DatasetFieldFilter implements Parcelable { 1465 1466 /** @hide */ 1467 @Nullable 1468 public final Pattern pattern; 1469 DatasetFieldFilter(@ullable Pattern pattern)1470 DatasetFieldFilter(@Nullable Pattern pattern) { 1471 this.pattern = pattern; 1472 } 1473 getPattern()1474 public @Nullable Pattern getPattern() { 1475 return pattern; 1476 } 1477 1478 @Override toString()1479 public String toString() { 1480 if (!sDebug) return super.toString(); 1481 1482 // Cannot log pattern because it could contain PII 1483 return pattern == null ? "null" : pattern.pattern().length() + "_chars"; 1484 } 1485 1486 @Override describeContents()1487 public int describeContents() { 1488 return 0; 1489 } 1490 1491 @Override writeToParcel(@onNull Parcel parcel, int flags)1492 public void writeToParcel(@NonNull Parcel parcel, int flags) { 1493 parcel.writeSerializable(pattern); 1494 } 1495 1496 @SuppressWarnings("hiding") 1497 public static final @android.annotation.NonNull Creator<DatasetFieldFilter> CREATOR = 1498 new Creator<DatasetFieldFilter>() { 1499 1500 @Override 1501 public DatasetFieldFilter createFromParcel(Parcel parcel) { 1502 return new DatasetFieldFilter((Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class)); 1503 } 1504 1505 @Override 1506 public DatasetFieldFilter[] newArray(int size) { 1507 return new DatasetFieldFilter[size]; 1508 } 1509 }; 1510 } 1511 } 1512