1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.autofillservice.cts.testcore; 17 18 import static android.autofillservice.cts.testcore.Helper.createInlinePresentation; 19 import static android.autofillservice.cts.testcore.Helper.createPresentation; 20 import static android.autofillservice.cts.testcore.Helper.getAutofillIds; 21 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import android.app.assist.AssistStructure; 25 import android.app.assist.AssistStructure.ViewNode; 26 import android.content.IntentSender; 27 import android.os.Bundle; 28 import android.service.autofill.Dataset; 29 import android.service.autofill.Field; 30 import android.service.autofill.FillCallback; 31 import android.service.autofill.FillContext; 32 import android.service.autofill.FillResponse; 33 import android.service.autofill.InlinePresentation; 34 import android.service.autofill.Presentations; 35 import android.service.autofill.SaveInfo; 36 import android.service.autofill.UserData; 37 import android.util.Log; 38 import android.util.Pair; 39 import android.view.autofill.AutofillId; 40 import android.view.autofill.AutofillManager; 41 import android.view.autofill.AutofillValue; 42 import android.widget.RemoteViews; 43 44 import androidx.annotation.NonNull; 45 import androidx.annotation.Nullable; 46 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.HashMap; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.function.Function; 53 import java.util.regex.Pattern; 54 55 /** 56 * Helper class used to produce a {@link FillResponse} based on expected fields that should be 57 * present in the {@link AssistStructure}. 58 * 59 * <p>Typical usage: 60 * 61 * <pre class="prettyprint"> 62 * InstrumentedAutoFillService.getReplier().addResponse(new CannedFillResponse.Builder() 63 * .addDataset(new CannedDataset.Builder("dataset_name") 64 * .setField("resource_id1", AutofillValue.forText("value1")) 65 * .setField("resource_id2", AutofillValue.forText("value2")) 66 * .build()) 67 * .build()); 68 * </pre class="prettyprint"> 69 */ 70 public final class CannedFillResponse { 71 72 private static final String TAG = CannedFillResponse.class.getSimpleName(); 73 74 private final ResponseType mResponseType; 75 private final List<CannedDataset> mDatasets; 76 private final String mFailureMessage; 77 private final int mSaveType; 78 private final String[] mRequiredSavableIds; 79 private final String[] mOptionalSavableIds; 80 private final AutofillId[] mRequiredSavableAutofillIds; 81 private final CharSequence mSaveDescription; 82 private final Bundle mExtras; 83 private final RemoteViews mPresentation; 84 private final InlinePresentation mInlinePresentation; 85 private final RemoteViews mHeader; 86 private final RemoteViews mFooter; 87 private final IntentSender mAuthentication; 88 private final String[] mAuthenticationIds; 89 private final String[] mIgnoredIds; 90 private final int mNegativeActionStyle; 91 private final IntentSender mNegativeActionListener; 92 private final int mPositiveActionStyle; 93 private final int mSaveInfoFlags; 94 private final int mFillResponseFlags; 95 private final AutofillId mSaveTriggerId; 96 private final long mDisableDuration; 97 private final String[] mFieldClassificationIds; 98 private final boolean mFieldClassificationIdsOverflow; 99 private final SaveInfoDecorator mSaveInfoDecorator; 100 private final UserData mUserData; 101 private final DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor; 102 private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor; 103 private final int[] mCancelIds; 104 private final String[] mDialogTriggerIds; 105 private final RemoteViews mDialogHeaderPresentation; 106 private final int mIconResourceId; 107 private final int mServiceDisplayNameResourceId; 108 private final boolean mShowFillDialogIcon; 109 private final boolean mShowSaveDialogIcon; 110 111 CannedFillResponse(Builder builder)112 private CannedFillResponse(Builder builder) { 113 mResponseType = builder.mResponseType; 114 mDatasets = builder.mDatasets; 115 mFailureMessage = builder.mFailureMessage; 116 mRequiredSavableIds = builder.mRequiredSavableIds; 117 mRequiredSavableAutofillIds = builder.mRequiredSavableAutofillIds; 118 mOptionalSavableIds = builder.mOptionalSavableIds; 119 mSaveDescription = builder.mSaveDescription; 120 mSaveType = builder.mSaveType; 121 mExtras = builder.mExtras; 122 mPresentation = builder.mPresentation; 123 mInlinePresentation = builder.mInlinePresentation; 124 mHeader = builder.mHeader; 125 mFooter = builder.mFooter; 126 mAuthentication = builder.mAuthentication; 127 mAuthenticationIds = builder.mAuthenticationIds; 128 mIgnoredIds = builder.mIgnoredIds; 129 mNegativeActionStyle = builder.mNegativeActionStyle; 130 mNegativeActionListener = builder.mNegativeActionListener; 131 mPositiveActionStyle = builder.mPositiveActionStyle; 132 mSaveInfoFlags = builder.mSaveInfoFlags; 133 mFillResponseFlags = builder.mFillResponseFlags; 134 mSaveTriggerId = builder.mSaveTriggerId; 135 mDisableDuration = builder.mDisableDuration; 136 mFieldClassificationIds = builder.mFieldClassificationIds; 137 mFieldClassificationIdsOverflow = builder.mFieldClassificationIdsOverflow; 138 mSaveInfoDecorator = builder.mSaveInfoDecorator; 139 mUserData = builder.mUserData; 140 mVisitor = builder.mVisitor; 141 mSaveInfoVisitor = builder.mSaveInfoVisitor; 142 mCancelIds = builder.mCancelIds; 143 mDialogTriggerIds = builder.mDialogTriggerIds; 144 mDialogHeaderPresentation = builder.mDialogHeaderPresentation; 145 mIconResourceId = builder.mIconResourceId; 146 mServiceDisplayNameResourceId = builder.mServiceDisplayNameResourceId; 147 mShowFillDialogIcon = builder.mShowFillDialogIcon; 148 mShowSaveDialogIcon = builder.mShowSaveDialogIcon; 149 } 150 151 /** 152 * Constant used to pass a {@code null} response to the 153 * {@link FillCallback#onSuccess(FillResponse)} method. 154 */ 155 public static final CannedFillResponse NO_RESPONSE = 156 new Builder(ResponseType.NULL).build(); 157 158 /** 159 * Constant used to fail the test when an expected request was made. 160 */ 161 public static final CannedFillResponse NO_MOAR_RESPONSES = 162 new Builder(ResponseType.NO_MORE).build(); 163 164 /** 165 * Constant used to emulate a timeout by not calling any method on {@link FillCallback}. 166 */ 167 public static final CannedFillResponse DO_NOT_REPLY_RESPONSE = 168 new Builder(ResponseType.TIMEOUT).build(); 169 170 /** 171 * Constant used to call {@link FillCallback#onFailure(CharSequence)} method. 172 */ 173 public static final CannedFillResponse FAIL = 174 new Builder(ResponseType.FAILURE).build(); 175 getFailureMessage()176 public String getFailureMessage() { 177 return mFailureMessage; 178 } 179 getResponseType()180 public ResponseType getResponseType() { 181 return mResponseType; 182 } 183 184 /** 185 * Creates a new response, replacing the dataset field ids by the real ids from the assist 186 * structure. 187 */ asFillResponse(@ullable List<FillContext> contexts, @NonNull Function<String, ViewNode> nodeResolver)188 public FillResponse asFillResponse(@Nullable List<FillContext> contexts, 189 @NonNull Function<String, ViewNode> nodeResolver) { 190 return asFillResponseWithAutofillId(contexts, (id)-> { 191 ViewNode node = nodeResolver.apply(id); 192 if (node == null) { 193 throw new AssertionError("No node with resource id " + id); 194 } 195 return node.getAutofillId(); 196 }); 197 } 198 asFillResponseWithAutofillId(@ullable List<FillContext> contexts, @NonNull Function<String, AutofillId> autofillIdResolver)199 public FillResponse asFillResponseWithAutofillId(@Nullable List<FillContext> contexts, 200 @NonNull Function<String, AutofillId> autofillIdResolver) { 201 return asFillResponseWithAutofillId(contexts, autofillIdResolver, 202 (cannedDataset) -> { 203 return cannedDataset.asDatasetWithAutofillIdResolver(autofillIdResolver); 204 }); 205 } 206 207 private Function<String, AutofillId> getAutofillIdResolver( 208 @NonNull Function<String, ViewNode> nodeResolver) { 209 return (id) -> { 210 ViewNode node = nodeResolver.apply(id); 211 if (node == null) { 212 throw new AssertionError("No node with resource id " + id); 213 } 214 return node.getAutofillId(); 215 }; 216 } 217 218 public FillResponse asPccFillResponse(@Nullable List<FillContext> contexts, 219 @NonNull Function<String, ViewNode> nodeResolver) { 220 final Function<String, AutofillId> autofillPccResolver = 221 (id)-> { 222 ViewNode node = nodeResolver.apply(id); 223 if (node == null) { 224 return null; 225 } 226 return node.getAutofillId(); 227 }; 228 return asFillResponseWithAutofillId( 229 contexts, 230 autofillPccResolver, 231 (cannedDataset) -> cannedDataset.asDatasetForPcc(autofillPccResolver)); 232 } 233 234 /** 235 * Creates a new response, replacing the dataset field ids by the real ids from the assist 236 * structure. 237 */ 238 public FillResponse asFillResponseWithAutofillId(@Nullable List<FillContext> contexts, 239 @NonNull Function<String, AutofillId> autofillIdResolver, 240 Function<CannedDataset, Dataset> cannedDatasetToDataset) { 241 final FillResponse.Builder builder = new FillResponse.Builder() 242 .setFlags(mFillResponseFlags); 243 if (mDatasets != null) { 244 for (CannedDataset cannedDataset : mDatasets) { 245 final Dataset dataset = cannedDatasetToDataset.apply(cannedDataset); 246 assertWithMessage("Cannot create dataset").that(dataset).isNotNull(); 247 builder.addDataset(dataset); 248 } 249 } 250 final SaveInfo.Builder saveInfoBuilder; 251 if (mRequiredSavableIds != null || mOptionalSavableIds != null 252 || mRequiredSavableAutofillIds != null || mSaveInfoDecorator != null) { 253 if (mRequiredSavableAutofillIds != null) { 254 saveInfoBuilder = new SaveInfo.Builder(mSaveType, mRequiredSavableAutofillIds); 255 } else { 256 saveInfoBuilder = mRequiredSavableIds == null || mRequiredSavableIds.length == 0 257 ? new SaveInfo.Builder(mSaveType) 258 : new SaveInfo.Builder(mSaveType, 259 getAutofillIds(autofillIdResolver, mRequiredSavableIds)); 260 } 261 262 saveInfoBuilder.setFlags(mSaveInfoFlags); 263 264 if (mOptionalSavableIds != null) { 265 saveInfoBuilder.setOptionalIds( 266 getAutofillIds(autofillIdResolver, mOptionalSavableIds)); 267 } 268 if (mSaveDescription != null) { 269 saveInfoBuilder.setDescription(mSaveDescription); 270 } 271 if (mNegativeActionListener != null) { 272 saveInfoBuilder.setNegativeAction(mNegativeActionStyle, mNegativeActionListener); 273 } 274 275 saveInfoBuilder.setPositiveAction(mPositiveActionStyle); 276 277 if (mSaveTriggerId != null) { 278 saveInfoBuilder.setTriggerId(mSaveTriggerId); 279 } 280 } else if (mSaveInfoFlags != 0) { 281 saveInfoBuilder = new SaveInfo.Builder(mSaveType).setFlags(mSaveInfoFlags); 282 } else { 283 saveInfoBuilder = null; 284 } 285 if (saveInfoBuilder != null) { 286 // TODO: merge decorator and visitor 287 if (mSaveInfoDecorator != null) { 288 mSaveInfoDecorator.decorate(saveInfoBuilder, autofillIdResolver); 289 } 290 if (mSaveInfoVisitor != null) { 291 Log.d(TAG, "Visiting saveInfo " + saveInfoBuilder); 292 mSaveInfoVisitor.visit(contexts, saveInfoBuilder); 293 } 294 final SaveInfo saveInfo = saveInfoBuilder.build(); 295 Log.d(TAG, "saveInfo:" + saveInfo); 296 builder.setSaveInfo(saveInfo); 297 } 298 if (mIgnoredIds != null) { 299 builder.setIgnoredIds(getAutofillIds(autofillIdResolver, mIgnoredIds)); 300 } 301 if (mAuthenticationIds != null) { 302 builder.setAuthentication(getAutofillIds(autofillIdResolver, mAuthenticationIds), 303 mAuthentication, mPresentation, mInlinePresentation); 304 } 305 if (mDisableDuration > 0) { 306 builder.disableAutofill(mDisableDuration); 307 } 308 if (mFieldClassificationIdsOverflow) { 309 final int length = UserData.getMaxFieldClassificationIdsSize() + 1; 310 final AutofillId[] fieldIds = new AutofillId[length]; 311 for (int i = 0; i < length; i++) { 312 fieldIds[i] = new AutofillId(i); 313 } 314 builder.setFieldClassificationIds(fieldIds); 315 } else if (mFieldClassificationIds != null) { 316 builder.setFieldClassificationIds( 317 getAutofillIds(autofillIdResolver, mFieldClassificationIds)); 318 } 319 if (mExtras != null) { 320 builder.setClientState(mExtras); 321 } 322 if (mHeader != null) { 323 builder.setHeader(mHeader); 324 } 325 if (mFooter != null) { 326 builder.setFooter(mFooter); 327 } 328 if (mUserData != null) { 329 builder.setUserData(mUserData); 330 } 331 if (mVisitor != null) { 332 Log.d(TAG, "Visiting " + builder); 333 mVisitor.visit(contexts, builder); 334 } 335 builder.setPresentationCancelIds(mCancelIds); 336 if (mDialogTriggerIds != null) { 337 builder.setFillDialogTriggerIds( 338 getAutofillIds(autofillIdResolver, mDialogTriggerIds)); 339 } 340 if (mDialogHeaderPresentation != null) { 341 builder.setDialogHeader(mDialogHeaderPresentation); 342 } 343 344 builder.setIconResourceId(mIconResourceId); 345 builder.setServiceDisplayNameResourceId(mServiceDisplayNameResourceId); 346 builder.setShowFillDialogIcon(mShowFillDialogIcon); 347 builder.setShowSaveDialogIcon(mShowSaveDialogIcon); 348 349 final FillResponse response = builder.build(); 350 Log.v(TAG, "Response: " + response); 351 return response; 352 } 353 354 @Override 355 public String toString() { 356 return "CannedFillResponse: [type=" + mResponseType 357 + ",datasets=" + mDatasets 358 + ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds) 359 + ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds) 360 + ", requiredSavableAutofillIds=" + Arrays.toString(mRequiredSavableAutofillIds) 361 + ", saveInfoFlags=" + mSaveInfoFlags 362 + ", fillResponseFlags=" + mFillResponseFlags 363 + ", failureMessage=" + mFailureMessage 364 + ", saveDescription=" + mSaveDescription 365 + ", hasPresentation=" + (mPresentation != null) 366 + ", hasInlinePresentation=" + (mInlinePresentation != null) 367 + ", hasHeader=" + (mHeader != null) 368 + ", hasFooter=" + (mFooter != null) 369 + ", hasAuthentication=" + (mAuthentication != null) 370 + ", authenticationIds=" + Arrays.toString(mAuthenticationIds) 371 + ", ignoredIds=" + Arrays.toString(mIgnoredIds) 372 + ", saveTriggerId=" + mSaveTriggerId 373 + ", disableDuration=" + mDisableDuration 374 + ", fieldClassificationIds=" + Arrays.toString(mFieldClassificationIds) 375 + ", fieldClassificationIdsOverflow=" + mFieldClassificationIdsOverflow 376 + ", saveInfoDecorator=" + mSaveInfoDecorator 377 + ", userData=" + mUserData 378 + ", visitor=" + mVisitor 379 + ", saveInfoVisitor=" + mSaveInfoVisitor 380 + "]"; 381 } 382 383 public enum ResponseType { 384 NORMAL, 385 NULL, 386 NO_MORE, 387 TIMEOUT, 388 FAILURE, 389 DELAY 390 } 391 392 public static final class Builder { 393 private final List<CannedDataset> mDatasets = new ArrayList<>(); 394 private final ResponseType mResponseType; 395 private String mFailureMessage; 396 private String[] mRequiredSavableIds; 397 private String[] mOptionalSavableIds; 398 private AutofillId[] mRequiredSavableAutofillIds; 399 private CharSequence mSaveDescription; 400 public int mSaveType = -1; 401 private Bundle mExtras; 402 private RemoteViews mPresentation; 403 private InlinePresentation mInlinePresentation; 404 private RemoteViews mFooter; 405 private RemoteViews mHeader; 406 private IntentSender mAuthentication; 407 private String[] mAuthenticationIds; 408 private String[] mIgnoredIds; 409 private int mNegativeActionStyle; 410 private IntentSender mNegativeActionListener; 411 private int mPositiveActionStyle; 412 private int mSaveInfoFlags; 413 private int mFillResponseFlags; 414 private AutofillId mSaveTriggerId; 415 private long mDisableDuration; 416 private String[] mFieldClassificationIds; 417 private boolean mFieldClassificationIdsOverflow; 418 private SaveInfoDecorator mSaveInfoDecorator; 419 private UserData mUserData; 420 private DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor; 421 private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor; 422 private int[] mCancelIds; 423 private String[] mDialogTriggerIds; 424 private RemoteViews mDialogHeaderPresentation; 425 private int mIconResourceId; 426 private int mServiceDisplayNameResourceId; 427 private boolean mShowFillDialogIcon = true; 428 private boolean mShowSaveDialogIcon = true; 429 430 431 public Builder(ResponseType type) { 432 mResponseType = type; 433 } 434 435 public Builder() { 436 this(ResponseType.NORMAL); 437 } 438 439 public Builder addDataset(CannedDataset dataset) { 440 assertWithMessage("already set failure").that(mFailureMessage).isNull(); 441 mDatasets.add(dataset); 442 return this; 443 } 444 445 public Builder setIconResourceId(int id) { 446 mIconResourceId = id; 447 return this; 448 } 449 450 public Builder setServiceDisplayNameResourceId(int id) { 451 mServiceDisplayNameResourceId = id; 452 return this; 453 } 454 455 public Builder setShowFillDialogIcon(boolean show) { 456 mShowFillDialogIcon = show; 457 return this; 458 } 459 460 public Builder setShowSaveDialogIcon(boolean show) { 461 mShowSaveDialogIcon = show; 462 return this; 463 } 464 465 /** 466 * Sets the required savable ids based on their {@code resourceId}. 467 */ 468 public Builder setRequiredSavableIds(int type, String... ids) { 469 mSaveType = type; 470 mRequiredSavableIds = ids; 471 return this; 472 } 473 474 /** 475 * Sets the valid Save types, for when PCC Detection is enabled 476 */ 477 public Builder setSaveTypes(int type) { 478 mSaveType = type; 479 return this; 480 } 481 482 public Builder setSaveInfoFlags(int flags) { 483 mSaveInfoFlags = flags; 484 return this; 485 } 486 487 public Builder setFillResponseFlags(int flags) { 488 mFillResponseFlags = flags; 489 return this; 490 } 491 492 /** 493 * Sets the optional savable ids based on they {@code resourceId}. 494 */ 495 public Builder setOptionalSavableIds(String... ids) { 496 mOptionalSavableIds = ids; 497 return this; 498 } 499 500 /** 501 * Sets the description passed to the {@link SaveInfo}. 502 */ 503 public Builder setSaveDescription(CharSequence description) { 504 mSaveDescription = description; 505 return this; 506 } 507 508 /** 509 * Sets the extra passed to {@link 510 * android.service.autofill.FillResponse.Builder#setClientState(Bundle)}. 511 */ 512 public Builder setExtras(Bundle data) { 513 mExtras = data; 514 return this; 515 } 516 517 /** 518 * Sets the view to present the response in the UI. 519 */ 520 public Builder setPresentation(RemoteViews presentation) { 521 mPresentation = presentation; 522 return this; 523 } 524 525 /** 526 * Sets the view to present the response in the UI. 527 */ 528 public Builder setInlinePresentation(InlinePresentation inlinePresentation) { 529 mInlinePresentation = inlinePresentation; 530 return this; 531 } 532 533 /** 534 * Sets views to present the response in the UI by the type. 535 */ 536 public Builder setPresentation(String message, boolean inlineMode) { 537 mPresentation = createPresentation(message); 538 if (inlineMode) { 539 mInlinePresentation = createInlinePresentation(message); 540 } 541 return this; 542 } 543 544 /** 545 * Sets the authentication intent. 546 */ 547 public Builder setAuthentication(IntentSender authentication, String... ids) { 548 mAuthenticationIds = ids; 549 mAuthentication = authentication; 550 return this; 551 } 552 553 /** 554 * Sets the ignored fields based on resource ids. 555 */ 556 public Builder setIgnoreFields(String...ids) { 557 mIgnoredIds = ids; 558 return this; 559 } 560 561 /** 562 * Sets the negative action spec. 563 */ 564 public Builder setNegativeAction(int style, IntentSender listener) { 565 mNegativeActionStyle = style; 566 mNegativeActionListener = listener; 567 return this; 568 } 569 570 /** 571 * Sets the positive action spec. 572 */ 573 public Builder setPositiveAction(int style) { 574 mPositiveActionStyle = style; 575 return this; 576 } 577 578 public CannedFillResponse build() { 579 return new CannedFillResponse(this); 580 } 581 582 /** 583 * Sets the response to call {@link FillCallback#onFailure(CharSequence)}. 584 */ 585 public Builder returnFailure(String message) { 586 assertWithMessage("already added datasets").that(mDatasets).isEmpty(); 587 mFailureMessage = message; 588 return this; 589 } 590 591 /** 592 * Sets the view that explicitly triggers save. 593 */ 594 public Builder setSaveTriggerId(AutofillId id) { 595 assertWithMessage("already set").that(mSaveTriggerId).isNull(); 596 mSaveTriggerId = id; 597 return this; 598 } 599 600 public Builder disableAutofill(long duration) { 601 assertWithMessage("already set").that(mDisableDuration).isEqualTo(0L); 602 mDisableDuration = duration; 603 return this; 604 } 605 606 /** 607 * Sets the ids used for field classification. 608 */ 609 public Builder setFieldClassificationIds(String... ids) { 610 assertWithMessage("already set").that(mFieldClassificationIds).isNull(); 611 mFieldClassificationIds = ids; 612 return this; 613 } 614 615 /** 616 * Forces the service to throw an exception when setting the fields classification ids. 617 */ 618 public Builder setFieldClassificationIdsOverflow() { 619 mFieldClassificationIdsOverflow = true; 620 return this; 621 } 622 623 public Builder setHeader(RemoteViews header) { 624 assertWithMessage("already set").that(mHeader).isNull(); 625 mHeader = header; 626 return this; 627 } 628 629 public Builder setFooter(RemoteViews footer) { 630 assertWithMessage("already set").that(mFooter).isNull(); 631 mFooter = footer; 632 return this; 633 } 634 635 public Builder setSaveInfoDecorator(SaveInfoDecorator decorator) { 636 assertWithMessage("already set").that(mSaveInfoDecorator).isNull(); 637 mSaveInfoDecorator = decorator; 638 return this; 639 } 640 641 /** 642 * Sets the package-specific UserData. 643 * 644 * <p>Overrides the default UserData for field classification. 645 */ 646 public Builder setUserData(UserData userData) { 647 assertWithMessage("already set").that(mUserData).isNull(); 648 mUserData = userData; 649 return this; 650 } 651 652 /** 653 * Sets a generic visitor for the "real" request and response. 654 * 655 * <p>Typically used in cases where the test need to infer data from the request to build 656 * the response. 657 */ 658 public Builder setVisitor( 659 @NonNull DoubleVisitor<List<FillContext>, FillResponse.Builder> visitor) { 660 mVisitor = visitor; 661 return this; 662 } 663 664 /** 665 * Sets a generic visitor for the "real" request and save info. 666 * 667 * <p>Typically used in cases where the test need to infer data from the request to build 668 * the response. 669 */ 670 public Builder setSaveInfoVisitor( 671 @NonNull DoubleVisitor<List<FillContext>, SaveInfo.Builder> visitor) { 672 mSaveInfoVisitor = visitor; 673 return this; 674 } 675 676 /** 677 * Sets targets that cancel current session 678 */ 679 public Builder setPresentationCancelIds(int[] ids) { 680 mCancelIds = ids; 681 return this; 682 } 683 684 /** 685 * Sets the id of views which trigger the fill dialog. 686 */ 687 public Builder setDialogTriggerIds(String... ids) { 688 mDialogTriggerIds = ids; 689 return this; 690 } 691 692 /** 693 * Sets the header of the fill dialog. 694 */ 695 public Builder setDialogHeader(RemoteViews header) { 696 mDialogHeaderPresentation = header; 697 return this; 698 } 699 } 700 701 /** 702 * Helper class used to produce a {@link Dataset} based on expected fields that should be 703 * present in the {@link AssistStructure}. 704 * 705 * <p>Typical usage: 706 * 707 * <pre class="prettyprint"> 708 * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder() 709 * .addDataset(new CannedDataset.Builder("dataset_name") 710 * .setField("resource_id1", AutofillValue.forText("value1")) 711 * .setField("resource_id2", AutofillValue.forText("value2")) 712 * .build()) 713 * .build()); 714 * </pre class="prettyprint"> 715 */ 716 public static class CannedDataset { 717 private final Map<String, AutofillValue> mFieldValues; 718 private final Map<String, RemoteViews> mFieldPresentations; 719 private final Map<String, RemoteViews> mFieldDialogPresentations; 720 private final Map<String, InlinePresentation> mFieldInlinePresentations; 721 private final Map<String, InlinePresentation> mFieldInlineTooltipPresentations; 722 private final Map<String, Pair<Boolean, Pattern>> mFieldFilters; 723 private final RemoteViews mPresentation; 724 private final RemoteViews mDialogPresentation; 725 private final InlinePresentation mInlinePresentation; 726 private final InlinePresentation mInlineTooltipPresentation; 727 private final IntentSender mAuthentication; 728 private final String mId; 729 730 private CannedDataset(Builder builder) { 731 mFieldValues = builder.mFieldValues; 732 mFieldPresentations = builder.mFieldPresentations; 733 mFieldDialogPresentations = builder.mFieldDialogPresentations; 734 mFieldInlinePresentations = builder.mFieldInlinePresentations; 735 mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations; 736 mFieldFilters = builder.mFieldFilters; 737 mPresentation = builder.mPresentation; 738 mDialogPresentation = builder.mDialogPresentation; 739 mInlinePresentation = builder.mInlinePresentation; 740 mInlineTooltipPresentation = builder.mInlineTooltipPresentation; 741 mAuthentication = builder.mAuthentication; 742 mId = builder.mId; 743 } 744 745 /** 746 * Creates a new dataset, replacing the field ids by the real ids from the assist structure. 747 */ 748 public Dataset asDatasetWithNodeResolver(Function<String, ViewNode> nodeResolver) { 749 return asDatasetWithAutofillIdResolver((id) -> { 750 ViewNode node = nodeResolver.apply(id); 751 if (node == null) { 752 throw new AssertionError("No node with resource id " + id); 753 } 754 return node.getAutofillId(); 755 }); 756 } 757 758 public Dataset asDatasetForPcc(Function<String, AutofillId> autofillIdResolver) { 759 final Presentations.Builder presentationsBuilder = new Presentations.Builder(); 760 if (mPresentation != null) { 761 presentationsBuilder.setMenuPresentation(mPresentation); 762 } 763 if (mDialogPresentation != null) { 764 presentationsBuilder.setDialogPresentation(mDialogPresentation); 765 } 766 if (mInlinePresentation != null) { 767 presentationsBuilder.setInlinePresentation(mInlinePresentation); 768 } 769 if (mInlineTooltipPresentation != null) { 770 presentationsBuilder.setInlineTooltipPresentation(mInlineTooltipPresentation); 771 } 772 773 Presentations presentations = null; 774 try { 775 presentations = presentationsBuilder.build(); 776 } catch (IllegalStateException e) { 777 // No presentation in presentationsBuilder, do nothing. 778 } 779 final Dataset.Builder builder = presentations != null 780 ? new Dataset.Builder(presentations) 781 : new Dataset.Builder(); 782 if (mFieldValues != null) { 783 for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) { 784 final Field.Builder fieldBuilder = new Field.Builder(); 785 final AutofillValue value = entry.getValue(); 786 final String id = entry.getKey(); 787 if (value != null) { 788 fieldBuilder.setValue(value); 789 } 790 final Presentations.Builder fieldPresentationsBuilder = 791 new Presentations.Builder(); 792 final RemoteViews presentation = mFieldPresentations.get(id); 793 if (presentation != null) { 794 fieldPresentationsBuilder.setMenuPresentation(presentation); 795 } 796 final RemoteViews dialogPresentation = mFieldDialogPresentations.get(id); 797 if (dialogPresentation != null) { 798 fieldPresentationsBuilder.setDialogPresentation(dialogPresentation); 799 } 800 final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id); 801 if (inlinePresentation != null) { 802 fieldPresentationsBuilder.setInlinePresentation(inlinePresentation); 803 } 804 final InlinePresentation tooltipPresentation = 805 mFieldInlineTooltipPresentations.get(id); 806 if (tooltipPresentation != null) { 807 fieldPresentationsBuilder.setInlineTooltipPresentation(tooltipPresentation); 808 } 809 try { 810 fieldBuilder.setPresentations(fieldPresentationsBuilder.build()); 811 } catch (IllegalStateException e) { 812 // no presentation in fieldPresentationsBuilder, nothing 813 } 814 final Pair<Boolean, Pattern> filter = mFieldFilters.get(id); 815 if (filter != null) { 816 fieldBuilder.setFilter(filter.second); 817 } 818 819 final AutofillId autofillId = autofillIdResolver.apply(id); 820 if (autofillId == null) { 821 // Treat the id as autofill hints 822 builder.setField(id, fieldBuilder.build()); 823 } else { 824 builder.setField(autofillId, fieldBuilder.build()); 825 } 826 } 827 } 828 builder.setId(mId).setAuthentication(mAuthentication); 829 return builder.build(); 830 } 831 832 /** 833 * Creates a new dataset, replacing the field ids by the real ids from the assist structure. 834 */ 835 public Dataset asDatasetWithAutofillIdResolver( 836 Function<String, AutofillId> autofillIdResolver) { 837 final Presentations.Builder presentationsBuilder = new Presentations.Builder(); 838 if (mPresentation != null) { 839 presentationsBuilder.setMenuPresentation(mPresentation); 840 } 841 if (mDialogPresentation != null) { 842 presentationsBuilder.setDialogPresentation(mDialogPresentation); 843 } 844 if (mInlinePresentation != null) { 845 presentationsBuilder.setInlinePresentation(mInlinePresentation); 846 } 847 if (mInlineTooltipPresentation != null) { 848 presentationsBuilder.setInlineTooltipPresentation(mInlineTooltipPresentation); 849 } 850 851 Presentations presentations = null; 852 try { 853 presentations = presentationsBuilder.build(); 854 } catch (IllegalStateException e) { 855 // No presentation in presentationsBuilder, do nothing. 856 } 857 final Dataset.Builder builder = presentations != null 858 ? new Dataset.Builder(presentations) 859 : new Dataset.Builder(); 860 861 if (mFieldValues != null) { 862 for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) { 863 final String id = entry.getKey(); 864 865 final AutofillId autofillId = autofillIdResolver.apply(id); 866 if (autofillId == null) { 867 throw new AssertionError("No node with resource id " + id); 868 } 869 final Field.Builder fieldBuilder = new Field.Builder(); 870 final AutofillValue value = entry.getValue(); 871 if (value != null) { 872 fieldBuilder.setValue(value); 873 } 874 875 final Presentations.Builder fieldPresentationsBuilder = 876 new Presentations.Builder(); 877 final RemoteViews presentation = mFieldPresentations.get(id); 878 if (presentation != null) { 879 fieldPresentationsBuilder.setMenuPresentation(presentation); 880 } 881 final RemoteViews dialogPresentation = mFieldDialogPresentations.get(id); 882 if (dialogPresentation != null) { 883 fieldPresentationsBuilder.setDialogPresentation(dialogPresentation); 884 } 885 final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id); 886 if (inlinePresentation != null) { 887 fieldPresentationsBuilder.setInlinePresentation(inlinePresentation); 888 } 889 final InlinePresentation tooltipPresentation = 890 mFieldInlineTooltipPresentations.get(id); 891 if (tooltipPresentation != null) { 892 fieldPresentationsBuilder.setInlineTooltipPresentation(tooltipPresentation); 893 } 894 try { 895 fieldBuilder.setPresentations(fieldPresentationsBuilder.build()); 896 } catch (IllegalStateException e) { 897 // no presentation in fieldPresentationsBuilder, nothing 898 } 899 final Pair<Boolean, Pattern> filter = mFieldFilters.get(id); 900 if (filter != null) { 901 fieldBuilder.setFilter(filter.second); 902 } 903 builder.setField(autofillId, fieldBuilder.build()); 904 } 905 } 906 builder.setId(mId).setAuthentication(mAuthentication); 907 return builder.build(); 908 } 909 910 @Override 911 public String toString() { 912 return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null) 913 + ", hasDialogPresentation=" + (mDialogPresentation != null) 914 + ", hasInlinePresentation=" + (mInlinePresentation != null) 915 + ", fieldPresentations=" + (mFieldPresentations) 916 + ", fieldDialogPresentations=" + (mFieldDialogPresentations) 917 + ", fieldInlinePresentations=" + (mFieldInlinePresentations) 918 + ", fieldTooltipInlinePresentations=" + (mFieldInlineTooltipPresentations) 919 + ", hasAuthentication=" + (mAuthentication != null) 920 + ", fieldValues=" + mFieldValues 921 + ", fieldFilters=" + mFieldFilters + "]"; 922 } 923 924 public static class Builder { 925 private final Map<String, AutofillValue> mFieldValues = new HashMap<>(); 926 private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>(); 927 private final Map<String, RemoteViews> mFieldDialogPresentations = new HashMap<>(); 928 private final Map<String, InlinePresentation> mFieldInlinePresentations = 929 new HashMap<>(); 930 private final Map<String, InlinePresentation> mFieldInlineTooltipPresentations = 931 new HashMap<>(); 932 private final Map<String, Pair<Boolean, Pattern>> mFieldFilters = new HashMap<>(); 933 934 private RemoteViews mPresentation; 935 private RemoteViews mDialogPresentation; 936 private InlinePresentation mInlinePresentation; 937 private IntentSender mAuthentication; 938 private String mId; 939 private InlinePresentation mInlineTooltipPresentation; 940 941 public Builder() { 942 943 } 944 945 public Builder(RemoteViews presentation) { 946 mPresentation = presentation; 947 } 948 949 /** 950 * Sets the canned value of a text field based on its {@code id}. 951 * 952 * <p>The meaning of the id is defined by the object using the canned dataset. 953 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 954 * {@link IdMode}. 955 */ 956 public Builder setField(String id, String text) { 957 return setField(id, AutofillValue.forText(text)); 958 } 959 960 /** 961 * Sets the canned value of a text field applicable for all hints. 962 * This is applicable to PCC related datasets entry only. 963 */ 964 public Builder setField(String text) { 965 return setField(AutofillManager.ANY_HINT, AutofillValue.forText(text)); 966 } 967 968 /** 969 * Sets the canned value of a text field based on its {@code id}. 970 * 971 * <p>The meaning of the id is defined by the object using the canned dataset. 972 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 973 * {@link IdMode}. 974 */ 975 public Builder setField(String id, String text, Pattern filter) { 976 return setField(id, AutofillValue.forText(text), true, filter); 977 } 978 979 public Builder setUnfilterableField(String id, String text) { 980 return setField(id, AutofillValue.forText(text), false, null); 981 } 982 983 /** 984 * Sets the canned value of a list field based on its its {@code id}. 985 * 986 * <p>The meaning of the id is defined by the object using the canned dataset. 987 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 988 * {@link IdMode}. 989 */ 990 public Builder setField(String id, int index) { 991 return setField(id, AutofillValue.forList(index)); 992 } 993 994 /** 995 * Sets the canned value of a toggle field based on its {@code id}. 996 * 997 * <p>The meaning of the id is defined by the object using the canned dataset. 998 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 999 * {@link IdMode}. 1000 */ 1001 public Builder setField(String id, boolean toggled) { 1002 return setField(id, AutofillValue.forToggle(toggled)); 1003 } 1004 1005 /** 1006 * Sets the canned value of a date field based on its {@code id}. 1007 * 1008 * <p>The meaning of the id is defined by the object using the canned dataset. 1009 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 1010 * {@link IdMode}. 1011 */ 1012 public Builder setField(String id, long date) { 1013 return setField(id, AutofillValue.forDate(date)); 1014 } 1015 1016 /** 1017 * Sets the canned value of a date field based on its {@code id}. 1018 * 1019 * <p>The meaning of the id is defined by the object using the canned dataset. 1020 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 1021 * {@link IdMode}. 1022 */ 1023 public Builder setField(String id, AutofillValue value) { 1024 mFieldValues.put(id, value); 1025 return this; 1026 } 1027 1028 /** 1029 * Sets the canned value of a date field based on its {@code id}. 1030 * 1031 * <p>The meaning of the id is defined by the object using the canned dataset. 1032 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 1033 * {@link IdMode}. 1034 */ 1035 public Builder setField(String id, AutofillValue value, boolean filterable, 1036 Pattern filter) { 1037 setField(id, value); 1038 mFieldFilters.put(id, new Pair<>(filterable, filter)); 1039 return this; 1040 } 1041 1042 /** 1043 * Sets the canned value of a field based on its {@code id}. 1044 * 1045 * <p>The meaning of the id is defined by the object using the canned dataset. 1046 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 1047 * {@link IdMode}. 1048 */ 1049 public Builder setField(String id, String text, RemoteViews presentation) { 1050 setField(id, text); 1051 mFieldPresentations.put(id, presentation); 1052 return this; 1053 } 1054 1055 /** 1056 * Sets the canned value of a field based on its {@code id}. 1057 * 1058 * <p>The meaning of the id is defined by the object using the canned dataset. 1059 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 1060 * {@link IdMode}. 1061 */ 1062 public Builder setField(String id, String text, RemoteViews presentation, 1063 RemoteViews dialogPresentation) { 1064 setField(id, text, presentation); 1065 mFieldDialogPresentations.put(id, dialogPresentation); 1066 return this; 1067 } 1068 1069 /** 1070 * Sets the canned value of a field based on its {@code id}. 1071 * 1072 * <p>The meaning of the id is defined by the object using the canned dataset. 1073 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 1074 * {@link IdMode}. 1075 */ 1076 public Builder setField(String id, String text, RemoteViews presentation, 1077 Pattern filter) { 1078 setField(id, text, presentation); 1079 mFieldFilters.put(id, new Pair<>(true, filter)); 1080 return this; 1081 } 1082 1083 /** 1084 * Sets the canned value of a field based on its {@code id}. 1085 * 1086 * <p>The meaning of the id is defined by the object using the canned dataset. 1087 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 1088 * {@link IdMode}. 1089 */ 1090 public Builder setField(String id, String text, RemoteViews presentation, 1091 InlinePresentation inlinePresentation) { 1092 setField(id, text); 1093 mFieldPresentations.put(id, presentation); 1094 mFieldInlinePresentations.put(id, inlinePresentation); 1095 return this; 1096 } 1097 1098 /** 1099 * Sets the canned value of a field based on its {@code id}. 1100 * 1101 * <p>The meaning of the id is defined by the object using the canned dataset. 1102 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 1103 * {@link IdMode}. 1104 */ 1105 public Builder setField(String id, String text, RemoteViews presentation, 1106 InlinePresentation inlinePresentation, 1107 InlinePresentation inlineTooltipPresentation) { 1108 setField(id, text, presentation, inlinePresentation); 1109 mFieldInlineTooltipPresentations.put(id, inlineTooltipPresentation); 1110 return this; 1111 } 1112 1113 /** 1114 * Sets the canned value of a field based on its {@code id}. 1115 * 1116 * <p>The meaning of the id is defined by the object using the canned dataset. 1117 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 1118 * {@link IdMode}. 1119 */ 1120 public Builder setField(String id, String text, RemoteViews presentation, 1121 InlinePresentation inlinePresentation, Pattern filter) { 1122 setField(id, text, presentation, inlinePresentation); 1123 mFieldFilters.put(id, new Pair<>(true, filter)); 1124 return this; 1125 } 1126 1127 /** 1128 * Sets the canned value of a field based on its {@code id}. 1129 * 1130 * <p>The meaning of the id is defined by the object using the canned dataset. 1131 * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on 1132 * {@link IdMode}. 1133 */ 1134 public Builder setField(String id, String text, RemoteViews presentation, 1135 InlinePresentation inlinePresentation, 1136 InlinePresentation inlineTooltipPresentation, 1137 Pattern filter) { 1138 setField(id, text, presentation, inlinePresentation, inlineTooltipPresentation); 1139 mFieldFilters.put(id, new Pair<>(true, filter)); 1140 1141 return this; 1142 } 1143 1144 /** 1145 * Sets the view to present the response in the UI. 1146 */ 1147 public Builder setPresentation(RemoteViews presentation) { 1148 mPresentation = presentation; 1149 return this; 1150 } 1151 1152 /** 1153 * Sets the view to present the response in the UI. 1154 */ 1155 public Builder setInlinePresentation(InlinePresentation inlinePresentation) { 1156 mInlinePresentation = inlinePresentation; 1157 return this; 1158 } 1159 1160 /** 1161 * Sets the inline tooltip to present the response in the UI. 1162 */ 1163 public Builder setInlineTooltipPresentation(InlinePresentation tooltip) { 1164 mInlineTooltipPresentation = tooltip; 1165 return this; 1166 } 1167 1168 public Builder setPresentation(String message, boolean inlineMode) { 1169 mPresentation = createPresentation(message); 1170 if (inlineMode) { 1171 mInlinePresentation = createInlinePresentation(message); 1172 } 1173 return this; 1174 } 1175 1176 /** 1177 * Sets the view to present the response in the UI. 1178 */ 1179 public Builder setDialogPresentation(RemoteViews presentation) { 1180 mDialogPresentation = presentation; 1181 return this; 1182 } 1183 1184 /** 1185 * Sets the authentication intent. 1186 */ 1187 public Builder setAuthentication(IntentSender authentication) { 1188 mAuthentication = authentication; 1189 return this; 1190 } 1191 1192 /** 1193 * Sets the name. 1194 */ 1195 public Builder setId(String id) { 1196 mId = id; 1197 return this; 1198 } 1199 1200 /** 1201 * Builds the canned dataset. 1202 */ 1203 public CannedDataset build() { 1204 return new CannedDataset(this); 1205 } 1206 } 1207 } 1208 1209 public interface SaveInfoDecorator { 1210 void decorate(SaveInfo.Builder builder, Function<String, AutofillId> nodeResolver); 1211 } 1212 } 1213