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  *   &lt;form&gt;
57  *     &lt;input type=&quot;text&quot; placeholder=&quot;username&quot;&gt;
58  *     &lt;input type=&quot;password&quot; placeholder=&quot;password&quot;&gt;
59  *     &lt;input type=&quot;submit&quot; value=&quot;submit&quot;&gt;
60  *   &lt;/form&gt;
61  * </code></pre>
62  * <p>
63  * With the document parsed and the login input fields identified, GeckoView
64  * dispatches a
65  * <code>StorageDelegate.onLoginFetch(&quot;example.com&quot;)</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(&quot;example.com&quot;)</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