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