1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 2 * vim: ts=4 sw=4 expandtab: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 package org.mozilla.geckoview; 8 9 import java.lang.annotation.Retention; 10 import java.lang.annotation.RetentionPolicy; 11 12 import androidx.annotation.AnyThread; 13 import androidx.annotation.IntDef; 14 import androidx.annotation.NonNull; 15 import androidx.annotation.Nullable; 16 import androidx.annotation.UiThread; 17 import android.util.Log; 18 19 import org.mozilla.gecko.EventDispatcher; 20 import org.mozilla.gecko.util.BundleEventListener; 21 import org.mozilla.gecko.util.EventCallback; 22 import org.mozilla.gecko.util.GeckoBundle; 23 24 /** 25 * The Autocomplete API provides a way to leverage Gecko's input form handling 26 * for autocompletion. 27 * 28 * <p> 29 * The API is split into two parts: 30 * 1. Storage-level delegates. 31 * 2. User-prompt delegates. 32 * </p> 33 * <p> 34 * The storage-level delegates connect Gecko mechanics to the app's storage, 35 * e.g., retrieving and storing of login entries. 36 * </p> 37 * <p> 38 * The user-prompt delegates propagate decisions to the app that could require 39 * user choice, e.g., saving or updating of login entries or the selection of 40 * a login entry out of multiple options. 41 * </p> 42 * <p> 43 * Throughout the documentation, we will refer to the filling out of input forms 44 * using two terms: 45 * 1. Autofill: automatic filling without user interaction. 46 * 2. Autocomplete: semi-automatic filling that requires user prompting for the 47 * selection. 48 * </p> 49 * <h2>Examples</h2> 50 * 51 * <h3>Autocomplete/Fetch API</h3> 52 * <p> 53 * GeckoView loads <code>https://example.com</code> which contains (for the 54 * purpose of this example) elements resembling a login form, e.g., 55 * <pre><code> 56 * <form> 57 * <input type="text" placeholder="username"> 58 * <input type="password" placeholder="password"> 59 * <input type="submit" value="submit"> 60 * </form> 61 * </code></pre> 62 * <p> 63 * With the document parsed and the login input fields identified, GeckoView 64 * dispatches a 65 * <code>StorageDelegate.onLoginFetch("example.com")</code> 66 * request to fetch logins for the given domain. 67 * </p> 68 * <p> 69 * Based on the provided login entries, GeckoView will attempt to autofill the 70 * login input fields, if there is only one suitable login entry option. 71 * </p> 72 * <p> 73 * In the case of multiple valid login entry options, GeckoView dispatches a 74 * <code>GeckoSession.PromptDelegate.onLoginSelect</code> request, which allows 75 * for user-choice delegation. 76 * </p> 77 * <p> 78 * Based on the returned login entries, GeckoView will attempt to 79 * autofill/autocomplete the login input fields. 80 * </p> 81 * 82 * <h3>Update API</h3> 83 * <p> 84 * When the user submits some login input fields, GeckoView dispatches another 85 * <code>StorageDelegate.onLoginFetch("example.com")</code> 86 * request to check whether the submitted login exists or whether it's a new or 87 * updated login entry. 88 * </p> 89 * <p> 90 * If the submitted login is already contained as-is in the collection returned 91 * by <code>onLoginFetch</code>, then GeckoView dispatches 92 * <code>StorageDelegate.onLoginUsed</code> with the submitted login 93 * entry. 94 * </p> 95 * <p> 96 * If the submitted login is a new or updated entry, GeckoView dispatches 97 * a sequence of requests to save/update the login entry, see the Save API 98 * example. 99 * </p> 100 * 101 * <h3>Save API</h3> 102 * 103 * <p> 104 * The user enters new or updated (password) login credentials in some login 105 * input fields and submits explicitely (submit action) or by navigation. 106 * GeckoView identifies the entered credentials and dispatches a 107 * <code>GeckoSession.PromptDelegate.onLoginSave(session, request)</code> 108 * with the provided credentials. 109 * </p> 110 * 111 * <p> 112 * The app may dismiss the prompt request via 113 * <code>return GeckoResult.fromValue(prompt.dismiss())</code> 114 * which terminates this saving request, or confirm it via 115 * <code>return GeckoResult.fromValue(prompt.confirm(login))</code> 116 * where <code>login</code> either holds the credentials originally provided by 117 * the prompt request (<code>prompt.logins[0]</code>) or a new or modified login 118 * entry. 119 * </p> 120 * <p> 121 * The login entry returned in a confirmed save prompt is used to request for 122 * saving in the runtime delegate via 123 * <code>StorageDelegate.onLoginSave(login)</code>. 124 * If the app has already stored the entry during the prompt request handling, 125 * it may ignore this storage saving request. 126 * </p> 127 * 128 * <br>@see GeckoRuntime#setAutocompleteStorageDelegate 129 * <br>@see GeckoSession#setPromptDelegate 130 * <br>@see GeckoSession.PromptDelegate#onLoginSave 131 * <br>@see GeckoSession.PromptDelegate#onLoginSelect 132 */ 133 public class Autocomplete { 134 private static final String LOGTAG = "Autocomplete"; 135 private static final boolean DEBUG = false; 136 Autocomplete()137 protected Autocomplete() {} 138 139 /** 140 * Holds credit card information for a specific entry. 141 */ 142 public static class CreditCard { 143 private static final String GUID_KEY = "guid"; 144 private static final String NAME_KEY = "name"; 145 private static final String NUMBER_KEY = "number"; 146 private static final String EXP_MONTH_KEY = "expMonth"; 147 private static final String EXP_YEAR_KEY = "expYear"; 148 149 /** 150 * The unique identifier for this login entry. 151 */ 152 public final @Nullable String guid; 153 154 /** 155 * The full name as it appears on the credit card. 156 */ 157 public final @NonNull String name; 158 159 /** 160 * The credit card number. 161 */ 162 public final @NonNull String number; 163 164 /** 165 * The expiration month. 166 */ 167 public final @NonNull String expirationMonth; 168 169 /** 170 * The expiration year. 171 */ 172 public final @NonNull String expirationYear; 173 174 // For tests only. 175 @AnyThread CreditCard()176 protected CreditCard() { 177 guid = null; 178 name = ""; 179 number = ""; 180 expirationMonth = ""; 181 expirationYear = ""; 182 } 183 184 @AnyThread CreditCard(final @NonNull GeckoBundle bundle)185 /* package */ CreditCard(final @NonNull GeckoBundle bundle) { 186 guid = bundle.getString(GUID_KEY); 187 name = bundle.getString(NAME_KEY, ""); 188 number = bundle.getString(NUMBER_KEY, ""); 189 expirationMonth = bundle.getString(EXP_MONTH_KEY, ""); 190 expirationYear = bundle.getString(EXP_YEAR_KEY, ""); 191 } 192 193 @Override 194 @AnyThread toString()195 public String toString() { 196 final StringBuilder builder = new StringBuilder("CreditCard {"); 197 builder 198 .append("guid=").append(guid) 199 .append(", name=").append(name) 200 .append(", number=").append(number) 201 .append(", expirationMonth=").append(expirationMonth) 202 .append(", expirationYear=").append(expirationYear) 203 .append("}"); 204 return builder.toString(); 205 } 206 207 @AnyThread toBundle()208 /* package */ @NonNull GeckoBundle toBundle() { 209 final GeckoBundle bundle = new GeckoBundle(7); 210 bundle.putString(GUID_KEY, guid); 211 bundle.putString(NAME_KEY, name); 212 bundle.putString(NUMBER_KEY, number); 213 bundle.putString(EXP_MONTH_KEY, expirationMonth); 214 bundle.putString(EXP_YEAR_KEY, expirationYear); 215 216 return bundle; 217 } 218 219 public static class Builder { 220 private final GeckoBundle mBundle; 221 222 @AnyThread Builder(final @NonNull GeckoBundle bundle)223 /* package */ Builder(final @NonNull GeckoBundle bundle) { 224 mBundle = new GeckoBundle(bundle); 225 } 226 227 @AnyThread 228 @SuppressWarnings("checkstyle:javadocmethod") Builder()229 public Builder() { 230 mBundle = new GeckoBundle(7); 231 } 232 233 /** 234 * Finalize the {@link CreditCard} instance. 235 * 236 * @return The {@link CreditCard} instance. 237 */ 238 @AnyThread build()239 public @NonNull CreditCard build() { 240 return new CreditCard(mBundle); 241 } 242 243 /** 244 * Set the unique identifier for this credit card entry. 245 * 246 * @param guid The unique identifier string. 247 * @return This {@link Builder} instance. 248 */ 249 @AnyThread guid(final @Nullable String guid)250 public @NonNull Builder guid(final @Nullable String guid) { 251 mBundle.putString(GUID_KEY, guid); 252 return this; 253 } 254 255 /** 256 * Set the name for this credit card entry. 257 * 258 * @param name The full name as it appears on the credit card. 259 * @return This {@link Builder} instance. 260 */ 261 @AnyThread name(final @Nullable String name)262 public @NonNull Builder name(final @Nullable String name) { 263 mBundle.putString(NAME_KEY, name); 264 return this; 265 } 266 267 /** 268 * Set the number for this credit card entry. 269 * 270 * @param number The credit card number string. 271 * @return This {@link Builder} instance. 272 */ 273 @AnyThread number(final @Nullable String number)274 public @NonNull Builder number(final @Nullable String number) { 275 mBundle.putString(NUMBER_KEY, number); 276 return this; 277 } 278 279 /** 280 * Set the expiration month for this credit card entry. 281 * 282 * @param expMonth The expiration month string. 283 * @return This {@link Builder} instance. 284 */ 285 @AnyThread expirationMonth(final @Nullable String expMonth)286 public @NonNull Builder expirationMonth(final @Nullable String expMonth) { 287 mBundle.putString(EXP_MONTH_KEY, expMonth); 288 return this; 289 } 290 291 /** 292 * Set the expiration year for this credit card entry. 293 * 294 * @param expYear The expiration year string. 295 * @return This {@link Builder} instance. 296 */ 297 @AnyThread expirationYear(final @Nullable String expYear)298 public @NonNull Builder expirationYear(final @Nullable String expYear) { 299 mBundle.putString(EXP_YEAR_KEY, expYear); 300 return this; 301 } 302 } 303 } 304 305 /** 306 * Holds address information for a specific entry. 307 */ 308 public static class Address { 309 private static final String GUID_KEY = "guid"; 310 private static final String NAME_KEY = "name"; 311 private static final String GIVEN_NAME_KEY = "givenName"; 312 private static final String ADDITIONAL_NAME_KEY = "additionalName"; 313 private static final String FAMILY_NAME_KEY = "familyName"; 314 private static final String ORGANIZATION_KEY = "organization"; 315 private static final String STREET_ADDRESS_KEY = "streetAddress"; 316 private static final String ADDRESS_LEVEL1_KEY = "addressLevel1"; 317 private static final String ADDRESS_LEVEL2_KEY = "addressLevel2"; 318 private static final String ADDRESS_LEVEL3_KEY = "addressLevel3"; 319 private static final String POSTAL_CODE_KEY = "postalCode"; 320 private static final String COUNTRY_KEY = "country"; 321 private static final String TEL_KEY = "tel"; 322 private static final String EMAIL_KEY = "email"; 323 private static final byte bundleCapacity = 14; 324 325 326 /** 327 * The unique identifier for this address entry. 328 */ 329 public final @Nullable String guid; 330 331 /** 332 * The full name. 333 */ 334 public final @NonNull String name; 335 336 /** 337 * The given (first) name. 338 */ 339 public final @NonNull String givenName; 340 341 /** 342 * An additional name, if available. 343 */ 344 public final @NonNull String additionalName; 345 346 /** 347 * The family name. 348 */ 349 public final @NonNull String familyName; 350 351 /** 352 * The name of the company, if applicable. 353 */ 354 public final @NonNull String organization; 355 356 /** 357 * The (multiline) street address. 358 */ 359 public final @NonNull String streetAddress; 360 361 /** 362 * The level 1 (province) address. 363 * Note: Only use if streetAddress is not provided. 364 */ 365 public final @NonNull String addressLevel1; 366 367 /** 368 * The level 2 (city/town) address. 369 * Note: Only use if streetAddress is not provided. 370 */ 371 public final @NonNull String addressLevel2; 372 373 /** 374 * The level 3 (suburb/sublocality) address. 375 * Note: Only use if streetAddress is not provided. 376 */ 377 public final @NonNull String addressLevel3; 378 379 /** 380 * The postal code. 381 */ 382 public final @NonNull String postalCode; 383 384 /** 385 * The country string in ISO 3166. 386 */ 387 public final @NonNull String country; 388 389 /** 390 * The telephone number string. 391 */ 392 public final @NonNull String tel; 393 394 /** 395 * The email address. 396 */ 397 public final @NonNull String email; 398 399 // For tests only. 400 @AnyThread Address()401 protected Address() { 402 guid = null; 403 name = ""; 404 givenName = ""; 405 additionalName = ""; 406 familyName = ""; 407 organization = ""; 408 streetAddress = ""; 409 addressLevel1 = ""; 410 addressLevel2 = ""; 411 addressLevel3 = ""; 412 postalCode = ""; 413 country = ""; 414 tel = ""; 415 email = ""; 416 } 417 418 @AnyThread Address(final @NonNull GeckoBundle bundle)419 /* package */ Address(final @NonNull GeckoBundle bundle) { 420 guid = bundle.getString(GUID_KEY); 421 name = bundle.getString(NAME_KEY); 422 givenName = bundle.getString(GIVEN_NAME_KEY); 423 additionalName = bundle.getString(ADDITIONAL_NAME_KEY); 424 familyName = bundle.getString(FAMILY_NAME_KEY); 425 organization = bundle.getString(ORGANIZATION_KEY); 426 streetAddress = bundle.getString(STREET_ADDRESS_KEY); 427 addressLevel1 = bundle.getString(ADDRESS_LEVEL1_KEY); 428 addressLevel2 = bundle.getString(ADDRESS_LEVEL2_KEY); 429 addressLevel3 = bundle.getString(ADDRESS_LEVEL3_KEY); 430 postalCode = bundle.getString(POSTAL_CODE_KEY); 431 country = bundle.getString(COUNTRY_KEY); 432 tel = bundle.getString(TEL_KEY); 433 email = bundle.getString(EMAIL_KEY); 434 } 435 436 @Override 437 @AnyThread toString()438 public String toString() { 439 final StringBuilder builder = new StringBuilder("Address {"); 440 builder 441 .append("guid=").append(guid) 442 .append(", givenName=").append(givenName) 443 .append(", additionalName=").append(additionalName) 444 .append(", familyName=").append(familyName) 445 .append(", organization=").append(organization) 446 .append(", streetAddress=").append(streetAddress) 447 .append(", addressLevel1=").append(addressLevel1) 448 .append(", addressLevel2=").append(addressLevel2) 449 .append(", addressLevel3=").append(addressLevel3) 450 .append(", postalCode=").append(postalCode) 451 .append(", country=").append(country) 452 .append(", tel=").append(tel) 453 .append(", email=").append(email) 454 .append("}"); 455 return builder.toString(); 456 } 457 458 @AnyThread toBundle()459 /* package */ @NonNull GeckoBundle toBundle() { 460 final GeckoBundle bundle = new GeckoBundle(bundleCapacity); 461 bundle.putString(GUID_KEY, guid); 462 bundle.putString(NAME_KEY, name); 463 bundle.putString(GIVEN_NAME_KEY, givenName); 464 bundle.putString(ADDITIONAL_NAME_KEY, additionalName); 465 bundle.putString(FAMILY_NAME_KEY, familyName); 466 bundle.putString(ORGANIZATION_KEY, organization); 467 bundle.putString(STREET_ADDRESS_KEY, streetAddress); 468 bundle.putString(ADDRESS_LEVEL1_KEY, addressLevel1); 469 bundle.putString(ADDRESS_LEVEL2_KEY, addressLevel2); 470 bundle.putString(ADDRESS_LEVEL3_KEY, addressLevel3); 471 bundle.putString(POSTAL_CODE_KEY, postalCode); 472 bundle.putString(COUNTRY_KEY, country); 473 bundle.putString(TEL_KEY, tel); 474 bundle.putString(EMAIL_KEY, email); 475 476 return bundle; 477 } 478 479 public static class Builder { 480 private final GeckoBundle mBundle; 481 482 @AnyThread Builder(final @NonNull GeckoBundle bundle)483 /* package */ Builder(final @NonNull GeckoBundle bundle) { 484 mBundle = new GeckoBundle(bundle); 485 } 486 487 @AnyThread 488 @SuppressWarnings("checkstyle:javadocmethod") Builder()489 public Builder() { 490 mBundle = new GeckoBundle(bundleCapacity); 491 } 492 493 /** 494 * Finalize the {@link Address} instance. 495 * 496 * @return The {@link Address} instance. 497 */ 498 @AnyThread build()499 public @NonNull Address build() { 500 return new Address(mBundle); 501 } 502 503 /** 504 * Set the unique identifier for this address entry. 505 * 506 * @param guid The unique identifier string. 507 * @return This {@link Builder} instance. 508 */ 509 @AnyThread guid(final @Nullable String guid)510 public @NonNull Builder guid(final @Nullable String guid) { 511 mBundle.putString(GUID_KEY, guid); 512 return this; 513 } 514 515 /** 516 * Set the full name for this address entry. 517 * 518 * @param name The full name string. 519 * @return This {@link Builder} instance. 520 */ 521 @AnyThread name(final @Nullable String name)522 public @NonNull Builder name(final @Nullable String name) { 523 mBundle.putString(NAME_KEY, name); 524 return this; 525 } 526 527 /** 528 * Set the given name for this address entry. 529 * 530 * @param givenName The given name string. 531 * @return This {@link Builder} instance. 532 */ 533 @AnyThread givenName(final @Nullable String givenName)534 public @NonNull Builder givenName(final @Nullable String givenName) { 535 mBundle.putString(GIVEN_NAME_KEY, givenName); 536 return this; 537 } 538 539 /** 540 * Set the additional name for this address entry. 541 * 542 * @param additionalName The additional name string. 543 * @return This {@link Builder} instance. 544 */ 545 @AnyThread additionalName(final @Nullable String additionalName)546 public @NonNull Builder additionalName(final @Nullable String additionalName) { 547 mBundle.putString(ADDITIONAL_NAME_KEY, additionalName); 548 return this; 549 } 550 551 /** 552 * Set the family name for this address entry. 553 * 554 * @param familyName The family name string. 555 * @return This {@link Builder} instance. 556 */ 557 @AnyThread familyName(final @Nullable String familyName)558 public @NonNull Builder familyName(final @Nullable String familyName) { 559 mBundle.putString(FAMILY_NAME_KEY, familyName); 560 return this; 561 } 562 563 /** 564 * Set the company name for this address entry. 565 * 566 * @param organization The company name string. 567 * @return This {@link Builder} instance. 568 */ 569 @AnyThread organization(final @Nullable String organization)570 public @NonNull Builder organization(final @Nullable String organization) { 571 mBundle.putString(ORGANIZATION_KEY, organization); 572 return this; 573 } 574 575 /** 576 * Set the street address for this address entry. 577 * 578 * @param streetAddress The street address string. 579 * @return This {@link Builder} instance. 580 */ 581 @AnyThread streetAddress(final @Nullable String streetAddress)582 public @NonNull Builder streetAddress(final @Nullable String streetAddress) { 583 mBundle.putString(STREET_ADDRESS_KEY, streetAddress); 584 return this; 585 } 586 587 /** 588 * Set the level 1 address for this address entry. 589 * 590 * @param addressLevel1 The level 1 address string. 591 * @return This {@link Builder} instance. 592 */ 593 @AnyThread addressLevel1(final @Nullable String addressLevel1)594 public @NonNull Builder addressLevel1(final @Nullable String addressLevel1) { 595 mBundle.putString(ADDRESS_LEVEL1_KEY, addressLevel1); 596 return this; 597 } 598 599 /** 600 * Set the level 2 address for this address entry. 601 * 602 * @param addressLevel2 The level 2 address string. 603 * @return This {@link Builder} instance. 604 */ 605 @AnyThread addressLevel2(final @Nullable String addressLevel2)606 public @NonNull Builder addressLevel2(final @Nullable String addressLevel2) { 607 mBundle.putString(ADDRESS_LEVEL2_KEY, addressLevel2); 608 return this; 609 } 610 611 /** 612 * Set the level 3 address for this address entry. 613 * 614 * @param addressLevel3 The level 3 address string. 615 * @return This {@link Builder} instance. 616 */ 617 @AnyThread addressLevel3(final @Nullable String addressLevel3)618 public @NonNull Builder addressLevel3(final @Nullable String addressLevel3) { 619 mBundle.putString(ADDRESS_LEVEL3_KEY, addressLevel3); 620 return this; 621 } 622 623 /** 624 * Set the postal code for this address entry. 625 * 626 * @param postalCode The postal code string. 627 * @return This {@link Builder} instance. 628 */ 629 @AnyThread postalCode(final @Nullable String postalCode)630 public @NonNull Builder postalCode(final @Nullable String postalCode) { 631 mBundle.putString(POSTAL_CODE_KEY, postalCode); 632 return this; 633 } 634 635 /** 636 * Set the country code for this address entry. 637 * 638 * @param country The country string. 639 * @return This {@link Builder} instance. 640 */ 641 @AnyThread country(final @Nullable String country)642 public @NonNull Builder country(final @Nullable String country) { 643 mBundle.putString(COUNTRY_KEY, country); 644 return this; 645 } 646 647 /** 648 * Set the telephone number for this address entry. 649 * 650 * @param tel The telephone number string. 651 * @return This {@link Builder} instance. 652 */ 653 @AnyThread tel(final @Nullable String tel)654 public @NonNull Builder tel(final @Nullable String tel) { 655 mBundle.putString(TEL_KEY, tel); 656 return this; 657 } 658 659 /** 660 * Set the email address for this address entry. 661 * 662 * @param email The email address string. 663 * @return This {@link Builder} instance. 664 */ 665 @AnyThread email(final @Nullable String email)666 public @NonNull Builder email(final @Nullable String email) { 667 mBundle.putString(EMAIL_KEY, email); 668 return this; 669 } 670 } 671 } 672 673 /** 674 * Holds login information for a specific entry. 675 */ 676 public static class LoginEntry { 677 private static final String GUID_KEY = "guid"; 678 private static final String ORIGIN_KEY = "origin"; 679 private static final String FORM_ACTION_ORIGIN_KEY = "formActionOrigin"; 680 private static final String HTTP_REALM_KEY = "httpRealm"; 681 private static final String USERNAME_KEY = "username"; 682 private static final String PASSWORD_KEY = "password"; 683 684 /** 685 * The unique identifier for this login entry. 686 */ 687 public final @Nullable String guid; 688 689 /** 690 * The origin this login entry applies to. 691 */ 692 public final @NonNull String origin; 693 694 /** 695 * The origin this login entry was submitted to. 696 * This only applies to form-based login entries. 697 * It's derived from the action attribute set on the form element. 698 */ 699 public final @Nullable String formActionOrigin; 700 701 /** 702 * The HTTP realm this login entry was requested for. 703 * This only applies to non-form-based login entries. 704 * It's derived from the WWW-Authenticate header set in a HTTP 401 705 * response, see RFC2617 for details. 706 */ 707 public final @Nullable String httpRealm; 708 709 /** 710 * The username for this login entry. 711 */ 712 public final @NonNull String username; 713 714 /** 715 * The password for this login entry. 716 */ 717 public final @NonNull String password; 718 719 // For tests only. 720 @AnyThread LoginEntry()721 protected LoginEntry() { 722 guid = null; 723 origin = ""; 724 formActionOrigin = null; 725 httpRealm = null; 726 username = ""; 727 password = ""; 728 } 729 730 @AnyThread LoginEntry(final @NonNull GeckoBundle bundle)731 /* package */ LoginEntry(final @NonNull GeckoBundle bundle) { 732 guid = bundle.getString(GUID_KEY); 733 origin = bundle.getString(ORIGIN_KEY); 734 formActionOrigin = bundle.getString(FORM_ACTION_ORIGIN_KEY); 735 httpRealm = bundle.getString(HTTP_REALM_KEY); 736 username = bundle.getString(USERNAME_KEY, ""); 737 password = bundle.getString(PASSWORD_KEY, ""); 738 } 739 740 @Override 741 @AnyThread toString()742 public String toString() { 743 final StringBuilder builder = new StringBuilder("LoginEntry {"); 744 builder 745 .append("guid=").append(guid) 746 .append(", origin=").append(origin) 747 .append(", formActionOrigin=").append(formActionOrigin) 748 .append(", httpRealm=").append(httpRealm) 749 .append(", username=").append(username) 750 .append(", password=").append(password) 751 .append("}"); 752 return builder.toString(); 753 } 754 755 @AnyThread toBundle()756 /* package */ @NonNull GeckoBundle toBundle() { 757 final GeckoBundle bundle = new GeckoBundle(6); 758 bundle.putString(GUID_KEY, guid); 759 bundle.putString(ORIGIN_KEY, origin); 760 bundle.putString(FORM_ACTION_ORIGIN_KEY, formActionOrigin); 761 bundle.putString(HTTP_REALM_KEY, httpRealm); 762 bundle.putString(USERNAME_KEY, username); 763 bundle.putString(PASSWORD_KEY, password); 764 765 return bundle; 766 } 767 768 public static class Builder { 769 private final GeckoBundle mBundle; 770 771 @AnyThread Builder(final @NonNull GeckoBundle bundle)772 /* package */ Builder(final @NonNull GeckoBundle bundle) { 773 mBundle = new GeckoBundle(bundle); 774 } 775 776 @AnyThread 777 @SuppressWarnings("checkstyle:javadocmethod") Builder()778 public Builder() { 779 mBundle = new GeckoBundle(6); 780 } 781 782 /** 783 * Finalize the {@link LoginEntry} instance. 784 * 785 * @return The {@link LoginEntry} instance. 786 */ 787 @AnyThread build()788 public @NonNull LoginEntry build() { 789 return new LoginEntry(mBundle); 790 } 791 792 /** 793 * Set the unique identifier for this login entry. 794 * 795 * @param guid The unique identifier string. 796 * @return This {@link Builder} instance. 797 */ 798 @AnyThread guid(final @Nullable String guid)799 public @NonNull Builder guid(final @Nullable String guid) { 800 mBundle.putString(GUID_KEY, guid); 801 return this; 802 } 803 804 /** 805 * Set the origin this login entry applies to. 806 * 807 * @param origin The origin string. 808 * @return This {@link Builder} instance. 809 */ 810 @AnyThread origin(final @NonNull String origin)811 public @NonNull Builder origin(final @NonNull String origin) { 812 mBundle.putString(ORIGIN_KEY, origin); 813 return this; 814 } 815 816 /** 817 * Set the origin this login entry was submitted to. 818 * 819 * @param formActionOrigin The form action origin string. 820 * @return This {@link Builder} instance. 821 */ 822 @AnyThread formActionOrigin( final @Nullable String formActionOrigin)823 public @NonNull Builder formActionOrigin( 824 final @Nullable String formActionOrigin) { 825 mBundle.putString(FORM_ACTION_ORIGIN_KEY, formActionOrigin); 826 return this; 827 } 828 829 /** 830 * Set the HTTP realm this login entry was requested for. 831 * 832 * @param httpRealm The HTTP realm string. 833 * @return This {@link Builder} instance. 834 */ 835 @AnyThread httpRealm(final @Nullable String httpRealm)836 public @NonNull Builder httpRealm(final @Nullable String httpRealm) { 837 mBundle.putString(HTTP_REALM_KEY, httpRealm); 838 return this; 839 } 840 841 /** 842 * Set the username for this login entry. 843 * 844 * @param username The username string. 845 * @return This {@link Builder} instance. 846 */ 847 @AnyThread username(final @NonNull String username)848 public @NonNull Builder username(final @NonNull String username) { 849 mBundle.putString(USERNAME_KEY, username); 850 return this; 851 } 852 853 /** 854 * Set the password for this login entry. 855 * 856 * @param password The password string. 857 * @return This {@link Builder} instance. 858 */ 859 @AnyThread password(final @NonNull String password)860 public @NonNull Builder password(final @NonNull String password) { 861 mBundle.putString(PASSWORD_KEY, password); 862 return this; 863 } 864 } 865 } 866 867 @Retention(RetentionPolicy.SOURCE) 868 @IntDef(flag = true, 869 value = { UsedField.PASSWORD }) 870 /* package */ @interface LSUsedField {} 871 872 // Sync with UsedField in GeckoViewAutocomplete.jsm. 873 /** 874 * Possible login entry field types for {@link StorageDelegate#onLoginUsed}. 875 */ 876 public static class UsedField { 877 /** 878 * The password field of a login entry. 879 */ 880 public static final int PASSWORD = 1; 881 UsedField()882 protected UsedField() {} 883 } 884 885 /** 886 * Implement this interface to handle runtime login storage requests. 887 * Login storage events include login entry requests for autofill and 888 * autocompletion of login input fields. 889 * This delegate is attached to the runtime via 890 * {@link GeckoRuntime#setAutocompleteStorageDelegate}. 891 */ 892 public interface StorageDelegate { 893 /** 894 * Request login entries for a given domain. 895 * While processing the web document, we have identified elements 896 * resembling login input fields suitable for autofill. 897 * We will attempt to match the provided login information to the 898 * identified input fields. 899 * 900 * @param domain The domain string for the requested logins. 901 * @return A {@link GeckoResult} that completes with an array of 902 * {@link LoginEntry} containing the existing logins for the 903 * given domain. 904 */ 905 @UiThread onLoginFetch( @onNull final String domain)906 default @Nullable GeckoResult<LoginEntry[]> onLoginFetch( 907 @NonNull final String domain) { 908 return null; 909 } 910 911 /** 912 * Request credit card entries. 913 * While processing the web document, we have identified elements 914 * resembling credit card input fields suitable for autofill. 915 * We will attempt to match the provided credit card information to the 916 * identified input fields. 917 * 918 * @return A {@link GeckoResult} that completes with an array of 919 * {@link CreditCard} containing the existing credit cards. 920 */ 921 @UiThread onCreditCardFetch()922 default @Nullable GeckoResult<CreditCard[]> onCreditCardFetch() { 923 return null; 924 } 925 926 /** 927 * Request address entries. 928 * While processing the web document, we have identified elements 929 * resembling address input fields suitable for autofill. 930 * We will attempt to match the provided address information to the 931 * identified input fields. 932 * 933 * @return A {@link GeckoResult} that completes with an array of 934 * {@link Address} containing the existing addresses. 935 */ 936 @UiThread onAddressFetch()937 default @Nullable GeckoResult<Address[]> onAddressFetch() { 938 return null; 939 } 940 941 /** 942 * Request saving or updating of the given login entry. 943 * This is triggered by confirming a 944 * {@link GeckoSession.PromptDelegate#onLoginSave onLoginSave} request. 945 * 946 * @param login The {@link LoginEntry} as confirmed by the prompt 947 * request. 948 */ 949 @UiThread onLoginSave(@onNull final LoginEntry login)950 default void onLoginSave(@NonNull final LoginEntry login) {} 951 952 /** 953 * Request saving or updating of the given address entry. 954 * This is triggered by confirming a 955 * {@link GeckoSession.PromptDelegate#onAddressSave onAddressSave} request. 956 * 957 * @param address The {@link Address} as confirmed by the prompt 958 * request. 959 */ 960 @UiThread onAddressSave(@onNull Address address)961 default void onAddressSave(@NonNull Address address) {} 962 963 /** 964 * Notify that the given login was used to autofill login input fields. 965 * This is triggered by autofilling elements with unmodified login 966 * entries as provided via {@link #onLoginFetch}. 967 * 968 * @param login The {@link LoginEntry} that was used for the 969 * autofilling. 970 * @param usedFields The login entry fields used for autofilling. 971 * A combination of {@link UsedField}. 972 */ 973 @UiThread onLoginUsed( @onNull final LoginEntry login, @LSUsedField final int usedFields)974 default void onLoginUsed( 975 @NonNull final LoginEntry login, 976 @LSUsedField final int usedFields) {} 977 } 978 979 /** 980 * @deprecated This API has been replaced by {@link StorageDelegate} and 981 * will be removed in GeckoView 93. 982 */ 983 @Deprecated @DeprecationSchedule(version = 93, id = "login-storage") 984 public interface LoginStorageDelegate extends StorageDelegate {} 985 986 /** 987 * Abstract base class for Autocomplete options. 988 * Extended by {@link Autocomplete.SaveOption} and 989 * {@link Autocomplete.SelectOption}. 990 */ 991 public abstract static class Option<T> { 992 /* package */ static final String VALUE_KEY = "value"; 993 /* package */ static final String HINT_KEY = "hint"; 994 995 public final @NonNull T value; 996 public final int hint; 997 998 @SuppressWarnings("checkstyle:javadocmethod") Option(final @NonNull T value, final int hint)999 public Option(final @NonNull T value, final int hint) { 1000 this.value = value; 1001 this.hint = hint; 1002 } 1003 1004 @AnyThread toBundle()1005 /* package */ abstract @NonNull GeckoBundle toBundle(); 1006 } 1007 1008 /** 1009 * Abstract base class for saving options. 1010 * Extended by {@link Autocomplete.LoginSaveOption}. 1011 */ 1012 public abstract static class SaveOption<T> extends Option<T> { 1013 @Retention(RetentionPolicy.SOURCE) 1014 @IntDef(flag = true, 1015 value = { Hint.NONE, Hint.GENERATED, Hint.LOW_CONFIDENCE }) 1016 /* package */ @interface SaveOptionHint {} 1017 1018 /** 1019 * Hint types for login saving requests. 1020 */ 1021 public static class Hint { 1022 public static final int NONE = 0; 1023 1024 /** 1025 * Auto-generated password. 1026 * Notify but do not prompt the user for saving. 1027 */ 1028 public static final int GENERATED = 1 << 0; 1029 1030 /** 1031 * Potentially non-login data. 1032 * The form data entered may be not login credentials but other 1033 * forms of input like credit card numbers. 1034 * Note that this could be valid login data in same cases, e.g., 1035 * some banks may expect credit card numbers in the username field. 1036 */ 1037 public static final int LOW_CONFIDENCE = 1 << 1; 1038 Hint()1039 protected Hint() {} 1040 } 1041 1042 @SuppressWarnings("checkstyle:javadocmethod") SaveOption( final @NonNull T value, final @SaveOptionHint int hint)1043 public SaveOption( 1044 final @NonNull T value, 1045 final @SaveOptionHint int hint) { 1046 super(value, hint); 1047 } 1048 } 1049 1050 /** 1051 * Abstract base class for saving options. 1052 * Extended by {@link Autocomplete.LoginSelectOption}. 1053 */ 1054 public abstract static class SelectOption<T> extends Option<T> { 1055 @Retention(RetentionPolicy.SOURCE) 1056 @IntDef(flag = true, 1057 value = { Hint.NONE, Hint.GENERATED, Hint.INSECURE_FORM, 1058 Hint.DUPLICATE_USERNAME, Hint.MATCHING_ORIGIN }) 1059 /* package */ @interface SelectOptionHint {} 1060 1061 /** 1062 * Hint types for selection requests. 1063 */ 1064 public static class Hint { 1065 public static final int NONE = 0; 1066 1067 /** 1068 * Auto-generated password. 1069 * A new password-only login entry containing a secure generated 1070 * password. 1071 */ 1072 public static final int GENERATED = 1 << 0; 1073 1074 /** 1075 * Insecure context. 1076 * The form or transmission mechanics are considered insecure. 1077 * This is the case when the form is served via http or submitted 1078 * insecurely. 1079 */ 1080 public static final int INSECURE_FORM = 1 << 1; 1081 1082 /** 1083 * The username is shared with another login entry. 1084 * There are multiple login entries in the options that share the 1085 * same username. You may have to disambiguate the login entry, 1086 * e.g., using the last date of modification and its origin. 1087 */ 1088 public static final int DUPLICATE_USERNAME = 1 << 2; 1089 1090 /** 1091 * The login entry's origin matches the login form origin. 1092 * The login was saved from the same origin it is being requested 1093 * for, rather than for a subdomain. 1094 */ 1095 public static final int MATCHING_ORIGIN = 1 << 3; 1096 } 1097 1098 @SuppressWarnings("checkstyle:javadocmethod") SelectOption( final @NonNull T value, final @SelectOptionHint int hint)1099 public SelectOption( 1100 final @NonNull T value, 1101 final @SelectOptionHint int hint) { 1102 super(value, hint); 1103 } 1104 1105 @Override toString()1106 public String toString() { 1107 final StringBuilder builder = new StringBuilder("SelectOption {"); 1108 builder 1109 .append("value=").append(value).append(", ") 1110 .append("hint=").append(hint) 1111 .append("}"); 1112 return builder.toString(); 1113 } 1114 } 1115 1116 /** 1117 * Holds information required to process login saving requests. 1118 */ 1119 public static class LoginSaveOption extends SaveOption<LoginEntry> { 1120 /** 1121 * Construct a login save option. 1122 * 1123 * @param value The {@link LoginEntry} login entry to be saved. 1124 * @param hint The {@link Hint} detailing the type of the option. 1125 */ LoginSaveOption( final @NonNull LoginEntry value, final @SaveOptionHint int hint)1126 /* package */ LoginSaveOption( 1127 final @NonNull LoginEntry value, 1128 final @SaveOptionHint int hint) { 1129 super(value, hint); 1130 } 1131 1132 /** 1133 * Construct a login save option. 1134 * 1135 * @param value The {@link LoginEntry} login entry to be saved. 1136 */ LoginSaveOption(final @NonNull LoginEntry value)1137 public LoginSaveOption(final @NonNull LoginEntry value) { 1138 this(value, Hint.NONE); 1139 } 1140 1141 @Override toBundle()1142 /* package */ @NonNull GeckoBundle toBundle() { 1143 final GeckoBundle bundle = new GeckoBundle(2); 1144 bundle.putBundle(VALUE_KEY, value.toBundle()); 1145 bundle.putInt(HINT_KEY, hint); 1146 return bundle; 1147 } 1148 } 1149 1150 /** 1151 * Holds information required to process address saving requests. 1152 */ 1153 public static class AddressSaveOption extends SaveOption<Address> { 1154 /** 1155 * Construct a address save option. 1156 * 1157 * @param value The {@link Address} address entry to be saved. 1158 * @param hint The {@link Hint} detailing the type of the option. 1159 */ AddressSaveOption( final @NonNull Address value, final @SaveOptionHint int hint)1160 /* package */ AddressSaveOption( 1161 final @NonNull Address value, 1162 final @SaveOptionHint int hint) { 1163 super(value, hint); 1164 } 1165 1166 /** 1167 * Construct an address save option. 1168 * 1169 * @param value The {@link Address} address entry to be saved. 1170 */ AddressSaveOption(final @NonNull Address value)1171 public AddressSaveOption(final @NonNull Address value) { 1172 this(value, Hint.NONE); 1173 } 1174 1175 @Override toBundle()1176 /* package */ @NonNull GeckoBundle toBundle() { 1177 final GeckoBundle bundle = new GeckoBundle(2); 1178 bundle.putBundle(VALUE_KEY, value.toBundle()); 1179 bundle.putInt(HINT_KEY, hint); 1180 return bundle; 1181 } 1182 } 1183 1184 /** 1185 * Holds information required to process login selection requests. 1186 */ 1187 public static class LoginSelectOption extends SelectOption<LoginEntry> { 1188 /** 1189 * Construct a login select option. 1190 * 1191 * @param value The {@link LoginEntry} login entry selection option. 1192 * @param hint The {@link Hint} detailing the type of the option. 1193 */ LoginSelectOption( final @NonNull LoginEntry value, final @SelectOptionHint int hint)1194 /* package */ LoginSelectOption( 1195 final @NonNull LoginEntry value, 1196 final @SelectOptionHint int hint) { 1197 super(value, hint); 1198 } 1199 1200 /** 1201 * Construct a login select option. 1202 * 1203 * @param value The {@link LoginEntry} login entry selection option. 1204 */ LoginSelectOption(final @NonNull LoginEntry value)1205 public LoginSelectOption(final @NonNull LoginEntry value) { 1206 this(value, Hint.NONE); 1207 } 1208 fromBundle( final @NonNull GeckoBundle bundle)1209 /* package */ static @NonNull LoginSelectOption fromBundle( 1210 final @NonNull GeckoBundle bundle) { 1211 final int hint = bundle.getInt("hint"); 1212 final LoginEntry value = new LoginEntry(bundle.getBundle("value")); 1213 1214 return new LoginSelectOption(value, hint); 1215 } 1216 1217 @Override toBundle()1218 /* package */ @NonNull GeckoBundle toBundle() { 1219 final GeckoBundle bundle = new GeckoBundle(2); 1220 bundle.putBundle(VALUE_KEY, value.toBundle()); 1221 bundle.putInt(HINT_KEY, hint); 1222 return bundle; 1223 } 1224 } 1225 1226 /** 1227 * Holds information required to process credit card selection requests. 1228 */ 1229 public static class CreditCardSelectOption extends SelectOption<CreditCard> { 1230 @Retention(RetentionPolicy.SOURCE) 1231 @IntDef(flag = true, 1232 value = { Hint.NONE, Hint.INSECURE_FORM }) 1233 /* package */ @interface CreditCardSelectHint {} 1234 1235 /** 1236 * Hint types for credit card selection requests. 1237 */ 1238 public static class Hint { 1239 public static final int NONE = 0; 1240 1241 /** 1242 * Insecure context. 1243 * The form or transmission mechanics are considered insecure. 1244 * This is the case when the form is served via http or submitted 1245 * insecurely. 1246 */ 1247 public static final int INSECURE_FORM = 1 << 1; 1248 } 1249 1250 /** 1251 * Construct a credit card select option. 1252 * 1253 * @param value The {@link LoginEntry} credit card entry selection option. 1254 * @param hint The {@link Hint} detailing the type of the option. 1255 */ CreditCardSelectOption( final @NonNull CreditCard value, final @CreditCardSelectHint int hint)1256 /* package */ CreditCardSelectOption( 1257 final @NonNull CreditCard value, 1258 final @CreditCardSelectHint int hint) { 1259 super(value, hint); 1260 } 1261 1262 /** 1263 * Construct a credit card select option. 1264 * 1265 * @param value The {@link CreditCard} credit card entry selection option. 1266 */ CreditCardSelectOption(final @NonNull CreditCard value)1267 public CreditCardSelectOption(final @NonNull CreditCard value) { 1268 this(value, Hint.NONE); 1269 } 1270 fromBundle( final @NonNull GeckoBundle bundle)1271 /* package */ static @NonNull CreditCardSelectOption fromBundle( 1272 final @NonNull GeckoBundle bundle) { 1273 final int hint = bundle.getInt("hint"); 1274 final CreditCard value = new CreditCard(bundle.getBundle("value")); 1275 1276 return new CreditCardSelectOption(value, hint); 1277 } 1278 1279 @Override toBundle()1280 /* package */ @NonNull GeckoBundle toBundle() { 1281 final GeckoBundle bundle = new GeckoBundle(2); 1282 bundle.putBundle(VALUE_KEY, value.toBundle()); 1283 bundle.putInt(HINT_KEY, hint); 1284 return bundle; 1285 } 1286 } 1287 1288 /** 1289 * Holds information required to process address selection requests. 1290 */ 1291 public static class AddressSelectOption extends SelectOption<Address> { 1292 @Retention(RetentionPolicy.SOURCE) 1293 @IntDef(flag = true, 1294 value = { Hint.NONE, Hint.INSECURE_FORM }) 1295 /* package */ @interface AddressSelectHint {} 1296 1297 /** 1298 * Hint types for credit card selection requests. 1299 */ 1300 public static class Hint { 1301 public static final int NONE = 0; 1302 1303 /** 1304 * Insecure context. 1305 * The form or transmission mechanics are considered insecure. 1306 * This is the case when the form is served via http or submitted 1307 * insecurely. 1308 */ 1309 public static final int INSECURE_FORM = 1 << 1; 1310 } 1311 1312 /** 1313 * Construct a credit card select option. 1314 * 1315 * @param value The {@link LoginEntry} credit card entry selection option. 1316 * @param hint The {@link Hint} detailing the type of the option. 1317 */ AddressSelectOption( final @NonNull Address value, final @AddressSelectHint int hint)1318 /* package */ AddressSelectOption( 1319 final @NonNull Address value, 1320 final @AddressSelectHint int hint) { 1321 super(value, hint); 1322 } 1323 1324 /** 1325 * Construct a address select option. 1326 * 1327 * @param value The {@link Address} address entry selection option. 1328 */ AddressSelectOption(final @NonNull Address value)1329 public AddressSelectOption(final @NonNull Address value) { 1330 this(value, Hint.NONE); 1331 } 1332 fromBundle( final @NonNull GeckoBundle bundle)1333 /* package */ static @NonNull AddressSelectOption fromBundle( 1334 final @NonNull GeckoBundle bundle) { 1335 final int hint = bundle.getInt("hint"); 1336 final Address value = new Address(bundle.getBundle("value")); 1337 1338 return new AddressSelectOption(value, hint); 1339 } 1340 1341 @Override toBundle()1342 /* package */ @NonNull GeckoBundle toBundle() { 1343 final GeckoBundle bundle = new GeckoBundle(2); 1344 bundle.putBundle(VALUE_KEY, value.toBundle()); 1345 bundle.putInt(HINT_KEY, hint); 1346 return bundle; 1347 } 1348 } 1349 1350 /* package */ final static class StorageProxy implements BundleEventListener { 1351 private static final String FETCH_LOGIN_EVENT = 1352 "GeckoView:Autocomplete:Fetch:Login"; 1353 private static final String FETCH_CREDIT_CARD_EVENT = 1354 "GeckoView:Autocomplete:Fetch:CreditCard"; 1355 private static final String FETCH_ADDRESS_EVENT = 1356 "GeckoView:Autocomplete:Fetch:Address"; 1357 private static final String SAVE_LOGIN_EVENT = 1358 "GeckoView:Autocomplete:Save:Login"; 1359 private static final String SAVE_ADDRESS_EVENT = 1360 "GeckoView:Autocomplete:Save:Address"; 1361 private static final String USED_LOGIN_EVENT = 1362 "GeckoView:Autocomplete:Used:Login"; 1363 1364 private @Nullable StorageDelegate mDelegate; 1365 StorageProxy()1366 public StorageProxy() {} 1367 registerListener()1368 private void registerListener() { 1369 EventDispatcher.getInstance().registerUiThreadListener( 1370 this, 1371 FETCH_LOGIN_EVENT, 1372 FETCH_CREDIT_CARD_EVENT, 1373 FETCH_ADDRESS_EVENT, 1374 SAVE_LOGIN_EVENT, 1375 SAVE_ADDRESS_EVENT, 1376 USED_LOGIN_EVENT); 1377 } 1378 unregisterListener()1379 private void unregisterListener() { 1380 EventDispatcher.getInstance().unregisterUiThreadListener( 1381 this, 1382 FETCH_LOGIN_EVENT, 1383 FETCH_CREDIT_CARD_EVENT, 1384 FETCH_ADDRESS_EVENT, 1385 SAVE_LOGIN_EVENT, 1386 SAVE_ADDRESS_EVENT, 1387 USED_LOGIN_EVENT); 1388 } 1389 setDelegate( final @Nullable StorageDelegate delegate)1390 public synchronized void setDelegate( 1391 final @Nullable StorageDelegate delegate) { 1392 if (mDelegate == delegate) { 1393 return; 1394 } 1395 if (mDelegate != null) { 1396 unregisterListener(); 1397 } 1398 1399 mDelegate = delegate; 1400 1401 if (mDelegate != null) { 1402 registerListener(); 1403 } 1404 } 1405 getDelegate()1406 public synchronized @Nullable StorageDelegate getDelegate() { 1407 return mDelegate; 1408 } 1409 1410 @Override // BundleEventListener handleMessage( final String event, final GeckoBundle message, final EventCallback callback)1411 public synchronized void handleMessage( 1412 final String event, 1413 final GeckoBundle message, 1414 final EventCallback callback) { 1415 if (DEBUG) { 1416 Log.d(LOGTAG, "handleMessage " + event); 1417 } 1418 1419 if (mDelegate == null) { 1420 if (callback != null) { 1421 callback.sendError("No StorageDelegate attached"); 1422 } 1423 return; 1424 } 1425 1426 if (FETCH_LOGIN_EVENT.equals(event)) { 1427 final String domain = message.getString("domain"); 1428 final GeckoResult<Autocomplete.LoginEntry[]> result = 1429 mDelegate.onLoginFetch(domain); 1430 1431 if (result == null) { 1432 callback.sendSuccess(new GeckoBundle[0]); 1433 return; 1434 } 1435 1436 callback.resolveTo(result.map(logins -> { 1437 if (logins == null) { 1438 return new GeckoBundle[0]; 1439 } 1440 1441 // This is a one-liner with streams (API level 24). 1442 final GeckoBundle[] loginBundles = 1443 new GeckoBundle[logins.length]; 1444 for (int i = 0; i < logins.length; ++i) { 1445 loginBundles[i] = logins[i].toBundle(); 1446 } 1447 1448 return loginBundles; 1449 })); 1450 } else if (FETCH_CREDIT_CARD_EVENT.equals(event)) { 1451 final GeckoResult<Autocomplete.CreditCard[]> result = 1452 mDelegate.onCreditCardFetch(); 1453 1454 if (result == null) { 1455 callback.sendSuccess(new GeckoBundle[0]); 1456 return; 1457 } 1458 1459 callback.resolveTo(result.map(creditCards -> { 1460 if (creditCards == null) { 1461 return new GeckoBundle[0]; 1462 } 1463 1464 // This is a one-liner with streams (API level 24). 1465 final GeckoBundle[] creditCardBundles = 1466 new GeckoBundle[creditCards.length]; 1467 for (int i = 0; i < creditCards.length; ++i) { 1468 creditCardBundles[i] = creditCards[i].toBundle(); 1469 } 1470 1471 return creditCardBundles; 1472 })); 1473 } else if (FETCH_ADDRESS_EVENT.equals(event)) { 1474 final GeckoResult<Autocomplete.Address[]> result = 1475 mDelegate.onAddressFetch(); 1476 1477 if (result == null) { 1478 callback.sendSuccess(new GeckoBundle[0]); 1479 return; 1480 } 1481 1482 callback.resolveTo(result.map(addresses -> { 1483 if (addresses == null) { 1484 return new GeckoBundle[0]; 1485 } 1486 1487 // This is a one-liner with streams (API level 24). 1488 final GeckoBundle[] addressBundles = 1489 new GeckoBundle[addresses.length]; 1490 for (int i = 0; i < addresses.length; ++i) { 1491 addressBundles[i] = addresses[i].toBundle(); 1492 } 1493 1494 return addressBundles; 1495 })); 1496 } else if (SAVE_LOGIN_EVENT.equals(event)) { 1497 final GeckoBundle loginBundle = message.getBundle("login"); 1498 final LoginEntry login = new LoginEntry(loginBundle); 1499 1500 mDelegate.onLoginSave(login); 1501 } else if (SAVE_ADDRESS_EVENT.equals(event)) { 1502 final GeckoBundle addressBundle = message.getBundle("address"); 1503 final Address address = new Address(addressBundle); 1504 1505 mDelegate.onAddressSave(address); 1506 } else if (USED_LOGIN_EVENT.equals(event)) { 1507 final GeckoBundle loginBundle = message.getBundle("login"); 1508 final LoginEntry login = new LoginEntry(loginBundle); 1509 final int fields = message.getInt("usedFields"); 1510 1511 mDelegate.onLoginUsed(login, fields); 1512 } 1513 } 1514 } 1515 } 1516