Autofill  Part 1 (#52126)

diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart
index d52c58c..8199457 100644
--- a/packages/flutter/lib/services.dart
+++ b/packages/flutter/lib/services.dart
@@ -11,6 +11,7 @@
 library services;
 
 export 'src/services/asset_bundle.dart';
+export 'src/services/autofill.dart';
 export 'src/services/binary_messenger.dart';
 export 'src/services/binding.dart';
 export 'src/services/clipboard.dart';
diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart
index c021b72..a00410f 100644
--- a/packages/flutter/lib/src/cupertino/text_field.dart
+++ b/packages/flutter/lib/src/cupertino/text_field.dart
@@ -268,6 +268,7 @@
     this.onTap,
     this.scrollController,
     this.scrollPhysics,
+    this.autofillHints,
   }) : assert(textAlign != null),
        assert(readOnly != null),
        assert(autofocus != null),
@@ -579,6 +580,9 @@
   /// {@macro flutter.material.textfield.onTap}
   final GestureTapCallback onTap;
 
+  /// {@macro flutter.widgets.editableText.autofillHints}
+  final Iterable<String> autofillHints;
+
   @override
   _CupertinoTextFieldState createState() => _CupertinoTextFieldState();
 
@@ -950,6 +954,7 @@
           scrollController: widget.scrollController,
           scrollPhysics: widget.scrollPhysics,
           enableInteractiveSelection: widget.enableInteractiveSelection,
+          autofillHints: widget.autofillHints,
         ),
       ),
     );
diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart
index cb72f58..62a6fe3 100644
--- a/packages/flutter/lib/src/material/text_field.dart
+++ b/packages/flutter/lib/src/material/text_field.dart
@@ -346,6 +346,7 @@
     this.buildCounter,
     this.scrollController,
     this.scrollPhysics,
+    this.autofillHints,
   }) : assert(textAlign != null),
        assert(readOnly != null),
        assert(autofocus != null),
@@ -710,6 +711,9 @@
   /// {@macro flutter.widgets.editableText.scrollController}
   final ScrollController scrollController;
 
+  /// {@macro flutter.widgets.editableText.autofillHints}
+  final Iterable<String> autofillHints;
+
   @override
   _TextFieldState createState() => _TextFieldState();
 
@@ -1049,6 +1053,7 @@
         dragStartBehavior: widget.dragStartBehavior,
         scrollController: widget.scrollController,
         scrollPhysics: widget.scrollPhysics,
+        autofillHints: widget.autofillHints,
         autocorrectionTextRectColor: autocorrectionTextRectColor,
       ),
     );
diff --git a/packages/flutter/lib/src/services/autofill.dart b/packages/flutter/lib/src/services/autofill.dart
new file mode 100644
index 0000000..bffa7e8
--- /dev/null
+++ b/packages/flutter/lib/src/services/autofill.dart
@@ -0,0 +1,811 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/foundation.dart';
+import 'text_input.dart';
+
+/// A collection of commonly used autofill hint strings on different platforms.
+///
+/// Each hint may not be supported on every platform, and may get translated to
+/// different strings on different platforms. Please refer to their documentation
+/// for what each value corresponds to on different platforms.
+class AutofillHints {
+  AutofillHints._();
+
+  /// The input field expects an address locality (city/town).
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY).
+  /// * iOS: [addressCity](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String addressCity = 'addressCity';
+
+  /// The input field expects a city name combined with a state name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [addressCityAndState](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String addressCityAndState = 'addressCityAndState';
+
+  /// The input field expects a region/state.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_REGION](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_REGION).
+  /// * iOS: [addressState](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String addressState = 'addressState';
+
+  /// The input field expects a person's full birth date.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_BIRTH_DATE_FULL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_FULL).
+  /// * web: ["bday"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String birthday = 'birthday';
+
+  /// The input field expects a person's birth day(of the month).
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_BIRTH_DATE_DAY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_DAY).
+  /// * web: ["bday-day"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String birthdayDay = 'birthdayDay';
+
+  /// The input field expects a person's birth month.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_BIRTH_DATE_MONTH](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_MONTH).
+  /// * web: ["bday-month"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String birthdayMonth = 'birthdayMonth';
+
+  /// The input field expects a person's birth year.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_BIRTH_DATE_YEAR](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_YEAR).
+  /// * web: ["bday-year"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String birthdayYear = 'birthdayYear';
+
+  /// The input field expects an
+  /// [ISO 3166-1-alpha-2](https://www.iso.org/standard/63545.html) country code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["country"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String countryCode = 'countryCode';
+
+  /// The input field expects a country name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY).
+  /// * iOS: [countryName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["country-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String countryName = 'countryName';
+
+  /// The input field expects a credit card expiration date.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_NUMBER).
+  /// * web: ["cc-exp"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardExpirationDate = 'creditCardExpirationDate';
+
+  /// The input field expects a credit card expiration day.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardExpirationDay = 'creditCardExpirationDay';
+
+  /// The input field expects a credit card expiration month.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH).
+  /// * web: ["cc-exp-month"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardExpirationMonth = 'creditCardExpirationMonth';
+
+  /// The input field expects a credit card expiration year.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR).
+  /// * web: ["cc-exp-year"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardExpirationYear = 'creditCardExpirationYear';
+
+  /// The input field expects the holder's last/family name as given on a credit
+  /// card.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["cc-family-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardFamilyName = 'creditCardFamilyName';
+
+  /// The input field expects the holder's first/given name as given on a credit
+  /// card.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["cc-given-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardGivenName = 'creditCardGivenName';
+
+  /// The input field expects the holder's middle name as given on a credit
+  /// card.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["cc-additional-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardMiddleName = 'creditCardMiddleName';
+
+  /// The input field expects the holder's full name as given on a credit card.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["cc-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardName = 'creditCardName';
+
+  /// The input field expects a credit card number.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_NUMBER).
+  /// * iOS: [creditCardNumber](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["cc-number"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardNumber = 'creditCardNumber';
+
+  /// The input field expects a credit card security code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE).
+  /// * web: ["cc-csc"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardSecurityCode = 'creditCardSecurityCode';
+
+  /// The input field expects the type of a credit card, for example "Visa".
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["cc-type"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardType = 'creditCardType';
+
+  /// The input field expects an email address.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_EMAIL_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_EMAIL_ADDRESS).
+  /// * iOS: [emailAddress](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["email"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String email = 'email';
+
+  /// The input field expects a person's last/family name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_FAMILY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_FAMILY).
+  /// * iOS: [familyName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["family-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String familyName = 'familyName';
+
+  /// The input field expects a street address that fully identifies a location.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS).
+  /// * iOS: [fullStreetAddress](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["street-address"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String fullStreetAddress = 'fullStreetAddress';
+
+  /// The input field expects a gender.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_GENDER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_GENDER).
+  /// * web: ["sex"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String gender = 'gender';
+
+  /// The input field expects a person's first/given name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_GIVEN](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_GIVEN).
+  /// * iOS: [givenName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["given-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String givenName = 'givenName';
+
+  /// The input field expects a URL representing an instant messaging protocol
+  /// endpoint.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["impp"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String impp = 'impp';
+
+  /// The input field expects a job title.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [jobTitle](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["organization-title"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String jobTitle = 'jobTitle';
+
+  /// The input field expects the preferred language of the user.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["language"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String language = 'language';
+
+  /// The input field expects a location, such as a point of interest, an
+  /// address,or another way to identify a location.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [location](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String location = 'location';
+
+  /// The input field expects a person's middle initial.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_MIDDLE_INITIAL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_MIDDLE_INITIAL).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String middleInitial = 'middleInitial';
+
+  /// The input field expects a person's middle name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_MIDDLE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_MIDDLE).
+  /// * iOS: [middleName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["additional-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String middleName = 'middleName';
+
+  /// The input field expects a person's full name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME).
+  /// * iOS: [name](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String name = 'name';
+
+  /// The input field expects a person's name prefix or title, such as "Dr.".
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_PREFIX](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_PREFIX).
+  /// * iOS: [namePrefix](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["honorific-prefix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String namePrefix = 'namePrefix';
+
+  /// The input field expects a person's name suffix, such as "Jr.".
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_SUFFIX](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_SUFFIX).
+  /// * iOS: [nameSuffix](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["honorific-suffix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String nameSuffix = 'nameSuffix';
+
+  /// The input field expects a newly created password for save/update.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_NEW_PASSWORD](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_PASSWORD).
+  /// * iOS: [newPassword](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["new-password"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String newPassword = 'newPassword';
+
+  /// The input field expects a newly created username for save/update.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_NEW_USERNAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_USERNAME).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String newUsername = 'newUsername';
+
+  /// The input field expects a nickname.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [nickname](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["nickname"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String nickname = 'nickname';
+
+  /// The input field expects a single-factor SMS login code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_SMS_OTP](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_SMS_OTP).
+  /// * iOS: [oneTimeCode](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["one-time-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String oneTimeCode = 'oneTimeCode';
+
+  /// The input field expects an organization name corresponding to the person,
+  /// address, or contact information in the other fields associated with this
+  /// field.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [organizationName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["organization"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String organizationName = 'organizationName';
+
+  /// The input field expects a password.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PASSWORD](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PASSWORD).
+  /// * iOS: [password](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["current-password"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String password = 'password';
+
+  /// The input field expects a photograph, icon, or other image corresponding
+  /// to the company, person, address, or contact information in the other
+  /// fields associated with this field.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["photo"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String photo = 'photo';
+
+  /// The input field expects a postal address.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String postalAddress = 'postalAddress';
+
+  /// The input field expects an auxiliary address details.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String postalAddressExtended = 'postalAddressExtended';
+
+  /// The input field expects an extended ZIP/POSTAL code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String postalAddressExtendedPostalCode = 'postalAddressExtendedPostalCode';
+
+  /// The input field expects a postal code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_CODE).
+  /// * iOS: [postalCode](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["postal-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String postalCode = 'postalCode';
+
+  /// The first administrative level in the address. This is typically the
+  /// province in which the address is located. In the United States, this would
+  /// be the state. In Switzerland, the canton. In the United Kingdom, the post
+  /// town.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["address-level1"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLevel1 = 'streetAddressLevel1';
+
+  /// The second administrative level, in addresses with at least two of them.
+  /// In countries with two administrative levels, this would typically be the
+  /// city, town, village, or other locality in which the address is located.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["address-level2"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLevel2 = 'streetAddressLevel2';
+
+  /// The third administrative level, in addresses with at least three
+  /// administrative levels.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["address-level3"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLevel3 = 'streetAddressLevel3';
+
+  /// The finest-grained administrative level, in addresses which have four
+  /// levels.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["address-level4"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLevel4 = 'streetAddressLevel4';
+
+  /// The input field expects the first line of a street address.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [streetAddressLine1](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["address-line1"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLine1 = 'streetAddressLine1';
+
+  /// The input field expects the second line of a street address.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [streetAddressLine2](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["address-line2"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLine2 = 'streetAddressLine2';
+
+  /// The input field expects the third line of a street address.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["address-line3"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLine3 = 'streetAddressLine3';
+
+  /// The input field expects a sublocality.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [sublocality](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String sublocality = 'sublocality';
+
+  /// The input field expects a telephone number.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PHONE_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NUMBER).
+  /// * iOS: [telephoneNumber](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["tel"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumber = 'telephoneNumber';
+
+  /// The input field expects a phone number's area code, with a country
+  /// -internal prefix applied if applicable.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["tel-area-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberAreaCode = 'telephoneNumberAreaCode';
+
+  /// The input field expects a phone number's country code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PHONE_COUNTRY_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_COUNTRY_CODE).
+  /// * web: ["tel-country-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberCountryCode = 'telephoneNumberCountryCode';
+
+  /// The input field expects the current device's phone number, usually for
+  /// Sign Up / OTP flows.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PHONE_NUMBER_DEVICE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NUMBER_DEVICE).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberDevice = 'telephoneNumberDevice';
+
+  /// The input field expects a phone number's internal extension code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["tel-extension"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberExtension = 'telephoneNumberExtension';
+
+  /// The input field expects a phone number without the country code and area
+  /// code components.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["tel-local"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberLocal = 'telephoneNumberLocal';
+
+  /// The input field expects the first part of the component of the telephone
+  /// number that follows the area code, when that component is split into two
+  /// components.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["tel-local-prefix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberLocalPrefix = 'telephoneNumberLocalPrefix';
+
+  /// The input field expects the second part of the component of the telephone
+  /// number that follows the area code, when that component is split into two
+  /// components.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["tel-local-suffix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberLocalSuffix = 'telephoneNumberLocalSuffix';
+
+  /// The input field expects a phone number without country code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PHONE_NATIONAL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NATIONAL).
+  /// * web: ["tel-national"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberNational = 'telephoneNumberNational';
+
+  /// The amount that the user would like for the transaction (e.g. when
+  /// entering a bid or sale price).
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["transaction-amount"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String transactionAmount = 'transactionAmount';
+
+  /// The currency that the user would prefer the transaction to use, in [ISO
+  /// 4217 currency code](https://www.iso.org/iso-4217-currency-codes.html).
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["transaction-currency"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String transactionCurrency = 'transactionCurrency';
+
+  /// The input field expects a URL.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [URL](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["url"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String url = 'url';
+
+  /// The input field expects a username or an account name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_NEW_USERNAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_USERNAME).
+  /// * iOS: [username](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["username"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String username = 'username';
+}
+
+/// A collection of autofill related information that represents an [AutofillClient].
+///
+/// Typically used in [TextInputConfiguration.autofillConfiguration].
+@immutable
+class AutofillConfiguration {
+  /// Creates autofill related configuration information that can be sent to the
+  /// platform.
+  const AutofillConfiguration({
+    @required this.uniqueIdentifier,
+    @required this.autofillHints,
+    this.currentEditingValue,
+  }) : assert(uniqueIdentifier != null),
+       assert(autofillHints != null);
+
+  /// A string that uniquely identifies the current [AutofillClient].
+  ///
+  /// The identifier needs to be unique within the [AutofillScope] for the
+  /// [AutofillClient] to receive the correct autofill value.
+  ///
+  /// Must not be null.
+  final String uniqueIdentifier;
+
+  /// A list of strings that helps the autofill service identify the type of the
+  /// [AutofillClient].
+  ///
+  /// Must not be null or empty.
+  ///
+  /// {@template flutter.services.autofill.autofillHints}
+  /// For the best results, hint strings need to be understood by the platform's
+  /// autofill service. The common values of hint strings can be found in
+  /// [AutofillHints], as well as the platforms that understand each of them.
+  ///
+  /// If an autofillable input field needs to use a custom hint that translate to
+  /// different strings on different platforms, the easiest way to achieve that
+  /// is to return different hint strings based on the value of
+  /// [defaultTargetPlatform].
+  ///
+  /// Each hint in the list, if not ignored, will be translated to the platform's
+  /// autofill hint type understood by its autofill services:
+  ///
+  /// * On iOS, only the first hint in the list is accounted for. The hint will
+  ///   be translated to a
+  ///   [UITextContentType](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  ///
+  /// * On Android, all hints in the list are translated to Android hint strings.
+  ///
+  /// * On web, only the first hint is accounted for and will be translated to
+  ///   an "autocomplete" string.
+  ///
+  /// See also:
+  ///
+  /// * [AutofillHints], a list of autofill hint strings that is predefined on at
+  ///   least one platform.
+  ///
+  /// * [UITextContentType](https://developer.apple.com/documentation/uikit/uitextcontenttype),
+  ///   the iOS equivalent.
+  ///
+  /// * Android [autofillHints](https://developer.android.com/reference/android/view/View#setAutofillHints(java.lang.String...)),
+  ///   the Android equivalent.
+  ///
+  /// * The [autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) attribute,
+  ///   the web equivalent.
+  /// {@endtemplate}
+  final List<String> autofillHints;
+
+  /// The current [TextEditingValue] of the [AutofillClient].
+  final TextEditingValue currentEditingValue;
+
+  /// Returns a representation of this object as a JSON object.
+  Map<String, dynamic> toJson() {
+    assert(autofillHints.isNotEmpty);
+    return <String, dynamic>{
+      'uniqueIdentifier': uniqueIdentifier,
+      'hints': autofillHints,
+      'editingValue': currentEditingValue.toJSON(),
+    };
+  }
+}
+
+/// An object that represents an autofillable input field in the autofill workflow.
+///
+/// An [AutofillClient] provides autofill-related information of the input field
+/// it represents to the platform, and consumes autofill inputs from the platform.
+abstract class AutofillClient {
+  /// The unique identifier of this [AutofillClient].
+  ///
+  /// Must not be null;
+  String get autofillId;
+
+  /// The [TextInputConfiguration] that describes this [AutofillClient].
+  ///
+  /// In order to participate in autofill, its
+  /// [TextInputConfiguration.autofillConfiguration] must not be null.
+  TextInputConfiguration get textInputConfiguration;
+
+  /// Requests this [AutofillClient] update its [TextEditingState] to the given
+  /// state.
+  void updateEditingValue(TextEditingValue newEditingValue);
+}
+
+/// An ordered group within which [AutofillClient]s are logically connected.
+///
+/// {@template flutter.services.autofill.AutofillScope}
+/// [AutofillClient]s within the same [AutofillScope] are isolated from other
+/// input fields during autofill. That is, when an autofillable [TextInputClient]
+/// gains focus, only the [AutofillClient]s within the same [AutofillScope] will
+/// be visible to the autofill service, in the same order as they appear in
+/// [autofillClients].
+///
+/// [AutofillScope] also allows [TextInput] to redirect autofill values from the
+/// platform to the [AutofillClient] with the given identifier, by calling
+/// [getAutofillClient].
+///
+/// An [AutofillClient] that's not tied to any [AutofillScope] will only
+/// participate in autofill if the autofill is directly triggered by its own
+/// [TextInputClient].
+/// {@endtemplate}
+abstract class AutofillScope {
+  /// Gets the [AutofillScope] associated with the given [autofillId], in
+  /// this [AutofillScope].
+  ///
+  /// Returns null if there's no matching [AutofillClient].
+  AutofillClient getAutofillClient(String autofillId);
+
+  /// The collection of [AutofillClient]s currently tied to this [AutofillScope].
+  ///
+  /// Every [AutofillClient] in this list must have autofill enabled (i.e. its
+  /// [AutofillClient.textInputConfiguration] must have a non-null
+  /// [AutofillConfiguration].)
+  Iterable<AutofillClient> get autofillClients;
+
+  /// Allows a [TextInputClient] to attach to this scope. This method should be
+  /// called in lieu of [TextInput.attach], when the [TextInputClient] wishes to
+  /// participate in autofill.
+  TextInputConnection attach(TextInputClient trigger, TextInputConfiguration configuration);
+}
+
+@immutable
+class _AutofillScopeTextInputConfiguration extends TextInputConfiguration {
+  _AutofillScopeTextInputConfiguration({
+    @required this.allConfigurations,
+    @required TextInputConfiguration currentClientConfiguration,
+  }) : assert(allConfigurations != null),
+       assert(currentClientConfiguration != null),
+       super(inputType: currentClientConfiguration.inputType,
+         obscureText: currentClientConfiguration.obscureText,
+         autocorrect: currentClientConfiguration.autocorrect,
+         smartDashesType: currentClientConfiguration.smartDashesType,
+         smartQuotesType: currentClientConfiguration.smartQuotesType,
+         enableSuggestions: currentClientConfiguration.enableSuggestions,
+         inputAction: currentClientConfiguration.inputAction,
+         textCapitalization: currentClientConfiguration.textCapitalization,
+         keyboardAppearance: currentClientConfiguration.keyboardAppearance,
+         actionLabel: currentClientConfiguration.actionLabel,
+         autofillConfiguration: currentClientConfiguration.autofillConfiguration,
+       );
+
+  final Iterable<TextInputConfiguration> allConfigurations;
+
+  @override
+  Map<String, dynamic> toJson() {
+    final Map<String, dynamic> result = super.toJson();
+    result['fields'] = allConfigurations
+      .map((TextInputConfiguration configuration) => configuration.toJson())
+      .toList(growable: false);
+    return result;
+  }
+}
+
+/// A partial implementation of [AutofillScope].
+///
+/// The mixin provides a default implementation for [AutofillScope.attach].
+mixin AutofillScopeMixin implements AutofillScope {
+  @override
+  TextInputConnection attach(TextInputClient trigger, TextInputConfiguration configuration) {
+    assert(trigger != null);
+    assert(
+      !autofillClients.any((AutofillClient client) => client.textInputConfiguration.autofillConfiguration == null),
+      'Every client in AutofillScope.autofillClients must enable autofill',
+    );
+    return TextInput.attach(
+      trigger,
+      _AutofillScopeTextInputConfiguration(
+        allConfigurations: autofillClients
+          .map((AutofillClient client) => client.textInputConfiguration),
+        currentClientConfiguration: configuration,
+      ),
+    );
+  }
+}
diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart
index 35bd2f4..2b7ff2c 100644
--- a/packages/flutter/lib/src/services/text_input.dart
+++ b/packages/flutter/lib/src/services/text_input.dart
@@ -16,6 +16,7 @@
 import 'package:flutter/foundation.dart';
 import 'package:vector_math/vector_math_64.dart' show Matrix4;
 
+import 'autofill.dart';
 import 'message_codec.dart';
 import 'platform_channel.dart';
 import 'system_channels.dart';
@@ -441,6 +442,7 @@
     this.inputAction = TextInputAction.done,
     this.keyboardAppearance = Brightness.light,
     this.textCapitalization = TextCapitalization.none,
+    this.autofillConfiguration,
   }) : assert(inputType != null),
        assert(obscureText != null),
        smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
@@ -464,6 +466,14 @@
   /// Defaults to true.
   final bool autocorrect;
 
+  /// The configuration to use for autofill.
+  ///
+  /// Defaults to null, in which case no autofill information will be provided
+  /// to the platform. This will prevent the corresponding input field from
+  /// participating in autofills triggered by other fields. Additionally, on
+  /// Android and web, setting [autofillConfiguration] to null disables autofill.
+  final AutofillConfiguration autofillConfiguration;
+
   /// {@template flutter.services.textInput.smartDashesType}
   /// Whether to allow the platform to automatically format dashes.
   ///
@@ -565,6 +575,7 @@
       'inputAction': inputAction.toString(),
       'textCapitalization': textCapitalization.toString(),
       'keyboardAppearance': keyboardAppearance.toString(),
+      if (autofillConfiguration != null) 'autofill': autofillConfiguration.toJson(),
     };
   }
 }
@@ -745,6 +756,21 @@
   /// const constructors so that they can be used in const expressions.
   const TextInputClient();
 
+  /// The current state of the [TextEditingValue] held by this client.
+  TextEditingValue get currentTextEditingValue;
+
+  /// The [AutofillScope] this [TextInputClient] belongs to, if any.
+  ///
+  /// It should return null if this [TextInputClient] does not need autofill
+  /// support. For a [TextInputClient] that supports autofill, returning null
+  /// causes it to participate in autofill alone.
+  ///
+  /// See also:
+  ///
+  /// * [AutofillGroup], a widget that creates an [AutofillScope] for its
+  ///   descendent autofillable [TextInputClient]s.
+  AutofillScope get currentAutofillScope;
+
   /// Requests that this client update its editing state to the given value.
   void updateEditingValue(TextEditingValue value);
 
@@ -754,9 +780,6 @@
   /// Updates the floating cursor position and state.
   void updateFloatingCursor(RawFloatingCursorPoint point);
 
-  /// The current state of the [TextEditingValue] held by this client.
-  TextEditingValue get currentTextEditingValue;
-
   /// Requests that this client display a prompt rectangle for the given text range,
   /// to indicate the range of text that will be changed by a pending autocorrection.
   ///
@@ -809,6 +832,17 @@
     TextInput._instance._show();
   }
 
+  /// Requests the platform autofill UI to appear.
+  ///
+  /// The call has no effect unless the currently attached client supports
+  /// autofill, and the platform has a standalone autofill UI (for example, this
+  /// call has no effect on iOS since its autofill UI is part of the software
+  /// keyboard).
+  void requestAutofill() {
+    assert(attached);
+    TextInput._instance._requestAutofill();
+  }
+
   /// Requests that the text input control change its internal state to match the given state.
   void setEditingState(TextEditingValue value) {
     assert(attached);
@@ -1065,6 +1099,22 @@
     }
 
     final List<dynamic> args = methodCall.arguments as List<dynamic>;
+
+    if (method == 'TextInputClient.updateEditingStateWithTag') {
+      final TextInputClient client = _currentConnection._client;
+      assert(client != null);
+      final AutofillScope scope = client.currentAutofillScope;
+      final Map<String, dynamic> editingValue = args[1] as Map<String, dynamic>;
+      for (final String tag in editingValue.keys) {
+        final TextEditingValue textEditingValue = TextEditingValue.fromJSON(
+          editingValue[tag] as Map<String, dynamic>,
+        );
+        scope?.getAutofillClient(tag)?.updateEditingValue(textEditingValue);
+      }
+
+      return;
+    }
+
     final int client = args[0] as int;
     // The incoming message was for a different client.
     if (client != _currentConnection._id)
@@ -1128,6 +1178,10 @@
     _channel.invokeMethod<void>('TextInput.show');
   }
 
+  void _requestAutofill() {
+    _channel.invokeMethod<void>('TextInput.requestAutofill');
+  }
+
   void _setEditableSizeAndTransform(Map<String, dynamic> args) {
     _channel.invokeMethod<void>(
       'TextInput.setEditableSizeAndTransform',
diff --git a/packages/flutter/lib/src/widgets/autofill.dart b/packages/flutter/lib/src/widgets/autofill.dart
new file mode 100644
index 0000000..e86198e
--- /dev/null
+++ b/packages/flutter/lib/src/widgets/autofill.dart
@@ -0,0 +1,227 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart';
+import 'framework.dart';
+
+export 'package:flutter/services.dart' show AutofillHints;
+
+/// An [AutofillScope] widget that groups [AutofillClient]s together.
+///
+/// [AutofillClient]s within the same [AutofillScope] must be built together, and
+/// they be will be autofilled together.
+///
+/// {@macro flutter.services.autofill.AutofillScope}
+///
+/// The [AutofillGroup] widget only knows about [AutofillClient]s registered to
+/// it using the [AutofillGroupState.register] API. Typically, [AutofillGroup]
+/// will not pick up [AutofillClient]s that are not mounted, for example, an
+/// [AutofillClient] within a [Scrollable] that has never been scrolled into the
+/// viewport. To workaround this problem, ensure clients in the same [AutofillGroup]
+/// are built together:
+///
+/// {@tool dartpad --template=stateful_widget_material}
+///
+/// An example form with autofillable fields grouped into different `AutofillGroup`s.
+///
+/// ```dart
+///  bool isSameAddress = true;
+///  final TextEditingController shippingAddress1 = TextEditingController();
+///  final TextEditingController shippingAddress2 = TextEditingController();
+///  final TextEditingController billingAddress1 = TextEditingController();
+///  final TextEditingController billingAddress2 = TextEditingController();
+///
+///  final TextEditingController creditCardNumber = TextEditingController();
+///  final TextEditingController creditCardSecurityCode = TextEditingController();
+///
+///  final TextEditingController phoneNumber = TextEditingController();
+///
+///  @override
+///  Widget build(BuildContext context) {
+///    return ListView(
+///      children: <Widget>[
+///        const Text('Shipping address'),
+///        // The address fields are grouped together as some platforms are capable
+///        // of autofilling all these fields in one go.
+///        AutofillGroup(
+///          child: Column(
+///            children: <Widget>[
+///              TextField(
+///                controller: shippingAddress1,
+///                autofillHints: <String>[AutofillHints.streetAddressLine1],
+///              ),
+///              TextField(
+///                controller: shippingAddress2,
+///                autofillHints: <String>[AutofillHints.streetAddressLine2],
+///              ),
+///            ],
+///          ),
+///        ),
+///        const Text('Billing address'),
+///        Checkbox(
+///          value: isSameAddress,
+///          onChanged: (bool newValue) {
+///            setState(() { isSameAddress = newValue; });
+///          },
+///        ),
+///        // Again the address fields are grouped together for the same reason.
+///        if (!isSameAddress) AutofillGroup(
+///          child: Column(
+///            children: <Widget>[
+///              TextField(
+///                controller: billingAddress1,
+///                autofillHints: <String>[AutofillHints.streetAddressLine1],
+///              ),
+///              TextField(
+///                controller: billingAddress2,
+///                autofillHints: <String>[AutofillHints.streetAddressLine2],
+///              ),
+///            ],
+///          ),
+///        ),
+///        const Text('Credit Card Information'),
+///        // The credit card number and the security code are grouped together as
+///        // some platforms are capable of autofilling both fields.
+///        AutofillGroup(
+///          child: Column(
+///            children: <Widget>[
+///              TextField(
+///                controller: creditCardNumber,
+///                autofillHints: <String>[AutofillHints.creditCardNumber],
+///              ),
+///              TextField(
+///                controller: creditCardSecurityCode,
+///                autofillHints: <String>[AutofillHints.creditCardSecurityCode],
+///              ),
+///            ],
+///          ),
+///        ),
+///        const Text('Contact Phone Number'),
+///        // The phone number field can still be autofilled despite lacking an
+///        // `AutofillScope`.
+///        TextField(
+///          controller: phoneNumber,
+///          autofillHints: <String>[AutofillHints.telephoneNumber],
+///        ),
+///      ],
+///    );
+///  }
+/// ```
+/// {@end-tool}
+class AutofillGroup extends StatefulWidget {
+  /// Creates a scope for autofillable input fields.
+  ///
+  /// The [child] argument must not be null.
+  const AutofillGroup({
+    Key key,
+    @required this.child,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// Returns the closest [AutofillGroupState] which encloses the given context.
+  ///
+  /// {@macro flutter.widgets.autofill.AutofillGroupState}
+  ///
+  /// See also:
+  ///
+  /// * [EditableTextState], where this method is used to retrive the closest
+  ///   [AutofillGroupState].
+  static AutofillGroupState of(BuildContext context) {
+    final _AutofillScope scope = context.dependOnInheritedWidgetOfExactType<_AutofillScope>();
+    return scope?._scope;
+  }
+
+  /// {@macro flutter.widgets.child}
+  final Widget child;
+
+  @override
+  AutofillGroupState createState() => AutofillGroupState();
+}
+
+/// State associated with an [AutofillGroup] widget.
+///
+/// {@template flutter.widgets.autofill.AutofillGroupState}
+/// An [AutofillGroupState] can be used to register an [AutofillClient] when it
+/// enters this [AutofillGroup] (for example, when an [EditableText] is mounted or
+/// reparented onto the [AutofillGroup]'s subtree), and unregister an
+/// [AutofillClient] when it exits (for example, when an [EditableText] gets
+/// unmounted or reparented out of the [AutofillGroup]'s subtree).
+///
+/// The [AutofillGroupState] class also provides an [attach] method that can be
+/// called by [TextInputClient]s that support autofill, instead of
+/// [TextInputClient.attach], to create a [TextInputConnection] to interact with
+/// the platform's text input system.
+/// {@endtemplate}
+///
+/// Typically obtained using [AutofillGroup.of].
+class AutofillGroupState extends State<AutofillGroup> with AutofillScopeMixin {
+  final Map<String, AutofillClient> _clients = <String, AutofillClient>{};
+
+  @override
+  AutofillClient getAutofillClient(String tag) => _clients[tag];
+
+  @override
+  Iterable<AutofillClient> get autofillClients {
+    return _clients.values
+      .where((AutofillClient client) => client?.textInputConfiguration?.autofillConfiguration != null);
+  }
+
+  /// Adds the [AutofillClient] to this [AutofillGroup].
+  ///
+  /// Typically, this is called by [TextInputClient]s that support autofill (for
+  /// example, [EditableTextState]) in [State.didChangeDependencies], when the
+  /// input field should be registered to a new [AutofillGroup].
+  ///
+  /// See also:
+  ///
+  /// * [EditableTextState.didChangeDependencies], where this method is called
+  ///   to update the current [AutofillScope] when needed.
+  void register(AutofillClient client) {
+    assert(client != null);
+    _clients.putIfAbsent(client.autofillId, () => client);
+  }
+
+  /// Removes an [AutofillClient] with the given [autofillId] from this
+  /// [AutofillGroup].
+  ///
+  /// Typically, this should be called by autofillable [TextInputClient]s in
+  /// [State.dispose] and [State.didChangeDependencies], when the input field
+  /// needs to be removed from the [AutofillGroup] it is currently registered to.
+  ///
+  /// See also:
+  ///
+  /// * [EditableTextState.didChangeDependencies], where this method is called
+  ///   to unregister from the previous [AutofillScope].
+  /// * [EditableTextState.dispose], where this method is called to unregister
+  ///   from the current [AutofillScope] when the widget is about to be removed
+  ///   from the tree.
+  void unregister(String autofillId) {
+    assert(autofillId != null && _clients.containsKey(autofillId));
+    _clients.remove(autofillId);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _AutofillScope(
+      autofillScopeState: this,
+      child: widget.child,
+    );
+  }
+}
+
+class _AutofillScope extends InheritedWidget {
+  const _AutofillScope({
+    Key key,
+    Widget child,
+    AutofillGroupState autofillScopeState,
+  }) : _scope = autofillScopeState,
+       super(key: key, child: child);
+
+  final AutofillGroupState _scope;
+
+  AutofillGroup get client => _scope.widget;
+
+  @override
+  bool updateShouldNotify(_AutofillScope old) => _scope != old._scope;
+}
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index e1ebab4..a1792bb 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -7,12 +7,13 @@
 import 'dart:ui' as ui hide TextStyle;
 
 import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart' show DragStartBehavior;
 import 'package:flutter/painting.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/services.dart';
-import 'package:flutter/gestures.dart' show DragStartBehavior;
 
+import 'autofill.dart';
 import 'automatic_keep_alive.dart';
 import 'basic.dart';
 import 'binding.dart';
@@ -29,8 +30,8 @@
 import 'text_selection.dart';
 import 'ticker_provider.dart';
 
-export 'package:flutter/services.dart' show TextEditingValue, TextSelection, TextInputType, SmartQuotesType, SmartDashesType;
 export 'package:flutter/rendering.dart' show SelectionChangedCause;
+export 'package:flutter/services.dart' show TextEditingValue, TextSelection, TextInputType, SmartQuotesType, SmartDashesType;
 
 /// Signature for the callback that reports when the user changes the selection
 /// (including the cursor location).
@@ -409,6 +410,7 @@
       paste: true,
       selectAll: true,
     ),
+    this.autofillHints,
   }) : assert(controller != null),
        assert(focusNode != null),
        assert(obscureText != null),
@@ -1079,6 +1081,23 @@
   /// {@macro flutter.rendering.editable.selectionEnabled}
   bool get selectionEnabled => enableInteractiveSelection;
 
+  /// {@template flutter.widgets.editableText.autofillHints}
+  /// A list of strings that helps the autofill service identify the type of this
+  /// text input.
+  ///
+  /// When set to null or empty, the text input will not send any autofill related
+  /// information to the platform. As a result, it will not participate in
+  /// autofills triggered by a different [AutofillClient], even if they're in the
+  /// same [AutofillScope]. Additionally, on Android and web, setting this to null
+  /// or empty will disable autofill for this text field.
+  ///
+  /// The minimum platform SDK version that supports Autofill is API level 26
+  /// for Android, and iOS 10.0 for iOS.
+  ///
+  /// {@macro flutter.services.autofill.autofillHints}
+  /// {@endtemplate}
+  final Iterable<String> autofillHints;
+
   @override
   EditableTextState createState() => EditableTextState();
 
@@ -1104,11 +1123,12 @@
     properties.add(DiagnosticsProperty<TextInputType>('keyboardType', keyboardType, defaultValue: null));
     properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
     properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
+    properties.add(DiagnosticsProperty<Iterable<String>>('autofillHints', autofillHints, defaultValue: null));
   }
 }
 
 /// State for a [EditableText].
-class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin<EditableText>, WidgetsBindingObserver, TickerProviderStateMixin<EditableText> implements TextInputClient, TextSelectionDelegate {
+class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin<EditableText>, WidgetsBindingObserver, TickerProviderStateMixin<EditableText> implements TextSelectionDelegate, TextInputClient, AutofillClient {
   Timer _cursorTimer;
   bool _targetCursorVisibility = false;
   final ValueNotifier<bool> _cursorVisibilityNotifier = ValueNotifier<bool>(true);
@@ -1128,6 +1148,10 @@
   bool _didAutoFocus = false;
   FocusAttachment _focusAttachment;
 
+  AutofillGroupState _currentAutofillScope;
+  @override
+  AutofillScope get currentAutofillScope => _currentAutofillScope;
+
   // This value is an eyeball estimation of the time it takes for the iOS cursor
   // to ease in and out.
   static const Duration _fadeDuration = Duration(milliseconds: 250);
@@ -1175,6 +1199,14 @@
   @override
   void didChangeDependencies() {
     super.didChangeDependencies();
+
+    final AutofillGroupState newAutofillGroup = AutofillGroup.of(context);
+    if (currentAutofillScope != newAutofillGroup) {
+      _currentAutofillScope?.unregister(autofillId);
+      _currentAutofillScope = newAutofillGroup;
+      newAutofillGroup?.register(this);
+    }
+
     if (!_didAutoFocus && widget.autofocus) {
       _didAutoFocus = true;
       SchedulerBinding.instance.addPostFrameCallback((_) {
@@ -1210,6 +1242,7 @@
       if (oldWidget.readOnly && _hasFocus)
         _openInputConnection();
     }
+
     if (widget.style != oldWidget.style) {
       final TextStyle style = widget.style;
       // The _textInputConnection will pick up the new style when it attaches in
@@ -1228,6 +1261,7 @@
 
   @override
   void dispose() {
+    _currentAutofillScope?.unregister(autofillId);
     widget.controller.removeListener(_didChangeTextEditingValue);
     _cursorBlinkOpacityController.removeListener(_onCursorColorTick);
     _floatingCursorResetController.removeListener(_onFloatingCursorResetTick);
@@ -1278,10 +1312,12 @@
 
     _formatAndSetValue(value);
 
-    // To keep the cursor from blinking while typing, we want to restart the
-    // cursor timer every time a new character is typed.
-    _stopCursorTimer(resetCharTicks: false);
-    _startCursorTimer();
+    if (_hasInputConnection) {
+      // To keep the cursor from blinking while typing, we want to restart the
+      // cursor timer every time a new character is typed.
+      _stopCursorTimer(resetCharTicks: false);
+      _startCursorTimer();
+    }
   }
 
   @override
@@ -1465,26 +1501,16 @@
     if (!_hasInputConnection) {
       final TextEditingValue localValue = _value;
       _lastFormattedUnmodifiedTextEditingValue = localValue;
-      _textInputConnection = TextInput.attach(
-        this,
-        TextInputConfiguration(
-          inputType: widget.keyboardType,
-          obscureText: widget.obscureText,
-          autocorrect: widget.autocorrect,
-          smartDashesType: widget.smartDashesType ?? (widget.obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
-          smartQuotesType: widget.smartQuotesType ?? (widget.obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
-          enableSuggestions: widget.enableSuggestions,
-          inputAction: widget.textInputAction ?? (widget.keyboardType == TextInputType.multiline
-              ? TextInputAction.newline
-              : TextInputAction.done
-          ),
-          textCapitalization: widget.textCapitalization,
-          keyboardAppearance: widget.keyboardAppearance,
-        ),
-      );
-      _textInputConnection.show();
 
+      _textInputConnection = (widget.autofillHints?.isNotEmpty ?? false) && currentAutofillScope != null
+        ? currentAutofillScope.attach(this, textInputConfiguration)
+        : TextInput.attach(this, textInputConfiguration);
+      _textInputConnection.show();
       _updateSizeAndTransform();
+      // Request autofill AFTER the size and the transform have been sent to the
+      // platform side.
+      _textInputConnection.requestAutofill();
+
       final TextStyle style = widget.style;
       _textInputConnection
         ..setStyle(
@@ -1904,6 +1930,33 @@
     }
   }
 
+  @override
+  String get autofillId => 'EditableText-$hashCode';
+
+  @override
+  TextInputConfiguration get textInputConfiguration {
+    final bool isAutofillEnabled = widget.autofillHints?.isNotEmpty ?? false;
+    return TextInputConfiguration(
+      inputType: widget.keyboardType,
+      obscureText: widget.obscureText,
+      autocorrect: widget.autocorrect,
+      smartDashesType: widget.smartDashesType ?? (widget.obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
+      smartQuotesType: widget.smartQuotesType ?? (widget.obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
+      enableSuggestions: widget.enableSuggestions,
+      inputAction: widget.textInputAction ?? (widget.keyboardType == TextInputType.multiline
+        ? TextInputAction.newline
+        : TextInputAction.done
+      ),
+      textCapitalization: widget.textCapitalization,
+      keyboardAppearance: widget.keyboardAppearance,
+      autofillConfiguration: !isAutofillEnabled ? null : AutofillConfiguration(
+        uniqueIdentifier: autofillId,
+        autofillHints: widget.autofillHints.toList(growable: false),
+        currentEditingValue: currentTextEditingValue,
+      ),
+    );
+  }
+
   // null if no promptRect should be shown.
   TextRange _currentPromptRectRange;
 
diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart
index 2db238b..38b3b5f 100644
--- a/packages/flutter/lib/widgets.dart
+++ b/packages/flutter/lib/widgets.dart
@@ -22,6 +22,7 @@
 export 'src/widgets/annotated_region.dart';
 export 'src/widgets/app.dart';
 export 'src/widgets/async.dart';
+export 'src/widgets/autofill.dart';
 export 'src/widgets/automatic_keep_alive.dart';
 export 'src/widgets/banner.dart';
 export 'src/widgets/basic.dart';
diff --git a/packages/flutter/test/services/autofill_test.dart b/packages/flutter/test/services/autofill_test.dart
new file mode 100644
index 0000000..e170ab0
--- /dev/null
+++ b/packages/flutter/test/services/autofill_test.dart
@@ -0,0 +1,238 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:convert' show utf8;
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('TextInput message channels', () {
+    FakeTextChannel fakeTextChannel;
+    FakeAutofillScope scope;
+
+    setUp(() {
+      fakeTextChannel = FakeTextChannel((MethodCall call) async {});
+      TextInput.setChannel(fakeTextChannel);
+      scope ??= FakeAutofillScope();
+      scope.clients.clear();
+    });
+
+    tearDown(() {
+      TextInputConnection.debugResetId();
+      TextInput.setChannel(SystemChannels.textInput);
+    });
+
+    test('mandatory fields are mandatory', () async {
+      AutofillConfiguration config;
+      try {
+        config = AutofillConfiguration(
+          uniqueIdentifier: null,
+          autofillHints: const <String>['test'],
+        );
+      } catch (e) {
+        expect(e.toString(), contains('uniqueIdentifier != null'));
+      }
+
+      expect(config, isNull);
+
+      try {
+        config = AutofillConfiguration(
+          uniqueIdentifier: 'id',
+          autofillHints: null,
+        );
+      } catch (e) {
+        expect(e.toString(), contains('autofillHints != null'));
+      }
+
+      expect(config, isNull);
+    });
+
+    test('throws if the hint list is empty', () async {
+      Map<String, dynamic> json;
+      try {
+        const AutofillConfiguration config = AutofillConfiguration(
+          uniqueIdentifier: 'id',
+          autofillHints: <String>[],
+        );
+
+        json = config.toJson();
+      } catch (e) {
+        expect(e.toString(), contains('isNotEmpty'));
+      }
+
+      expect(json, isNull);
+    });
+
+    test(
+      'AutofillClients send the correct configuration to the platform'
+      'and responds to updateEditingStateWithTag method correctly',
+      () async {
+        final FakeAutofillClient client1 = FakeAutofillClient(const TextEditingValue(text: 'test1'));
+        final FakeAutofillClient client2 = FakeAutofillClient(const TextEditingValue(text: 'test2'));
+
+        client1.textInputConfiguration = TextInputConfiguration(
+          autofillConfiguration: AutofillConfiguration(
+            uniqueIdentifier: client1.autofillId,
+            autofillHints: const <String>['client1'],
+            currentEditingValue: client1.currentTextEditingValue,
+          ),
+        );
+
+        client2.textInputConfiguration = TextInputConfiguration(
+          autofillConfiguration: AutofillConfiguration(
+            uniqueIdentifier: client2.autofillId,
+            autofillHints: const <String>['client2'],
+            currentEditingValue: client2.currentTextEditingValue,
+          ),
+        );
+
+        scope.register(client1);
+        scope.register(client2);
+        client1.currentAutofillScope = scope;
+        client2.currentAutofillScope = scope;
+
+        scope.attach(client1, client1.textInputConfiguration);
+
+        final Map<String, dynamic> expectedConfiguration = client1.textInputConfiguration.toJson();
+        expectedConfiguration['fields'] = <Map<String, dynamic>>[
+          client1.textInputConfiguration.toJson(),
+          client2.textInputConfiguration.toJson(),
+        ];
+
+        fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
+          MethodCall('TextInput.setClient', <dynamic>[1, expectedConfiguration]),
+        ]);
+
+        const TextEditingValue text2 = TextEditingValue(text: 'Text 2');
+        fakeTextChannel.incoming(MethodCall(
+          'TextInputClient.updateEditingStateWithTag',
+          <dynamic>[0, <String, dynamic>{ client2.autofillId : text2.toJSON() }],
+        ));
+
+        expect(client2.currentTextEditingValue, text2);
+    });
+  });
+}
+
+class FakeAutofillClient implements TextInputClient, AutofillClient {
+  FakeAutofillClient(this.currentTextEditingValue);
+
+  @override
+  String get autofillId => hashCode.toString();
+
+  @override
+  TextInputConfiguration textInputConfiguration;
+
+  @override
+  void updateEditingValue(TextEditingValue newEditingValue) {
+    currentTextEditingValue = newEditingValue;
+    latestMethodCall = 'updateEditingValue';
+  }
+
+  @override
+  AutofillScope currentAutofillScope;
+
+  String latestMethodCall = '';
+
+  @override
+  TextEditingValue currentTextEditingValue;
+
+  @override
+  void performAction(TextInputAction action) {
+    latestMethodCall = 'performAction';
+  }
+
+  @override
+  void updateFloatingCursor(RawFloatingCursorPoint point) {
+    latestMethodCall = 'updateFloatingCursor';
+  }
+
+  @override
+  void connectionClosed() {
+    latestMethodCall = 'connectionClosed';
+  }
+
+  @override
+  void showAutocorrectionPromptRect(int start, int end) {
+    latestMethodCall = 'showAutocorrectionPromptRect';
+  }
+}
+
+class FakeAutofillScope with AutofillScopeMixin implements AutofillScope {
+  final Map<String, AutofillClient> clients = <String, AutofillClient>{};
+
+  @override
+  Iterable<AutofillClient> get autofillClients => clients.values;
+
+  @override
+  AutofillClient getAutofillClient(String autofillId) => clients[autofillId];
+
+  void register(AutofillClient client) {
+    clients.putIfAbsent(client.autofillId, () => client);
+  }
+}
+
+class FakeTextChannel implements MethodChannel {
+  FakeTextChannel(this.outgoing) : assert(outgoing != null);
+
+  Future<dynamic> Function(MethodCall) outgoing;
+  Future<void> Function(MethodCall) incoming;
+
+  List<MethodCall> outgoingCalls = <MethodCall>[];
+
+  @override
+  BinaryMessenger get binaryMessenger => throw UnimplementedError();
+
+  @override
+  MethodCodec get codec => const JSONMethodCodec();
+
+  @override
+  Future<List<T>> invokeListMethod<T>(String method, [dynamic arguments]) => throw UnimplementedError();
+
+  @override
+  Future<Map<K, V>> invokeMapMethod<K, V>(String method, [dynamic arguments]) => throw UnimplementedError();
+
+  @override
+  Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {
+    final MethodCall call = MethodCall(method, arguments);
+    outgoingCalls.add(call);
+    return await outgoing(call) as T;
+  }
+
+  @override
+  String get name => 'flutter/textinput';
+
+  @override
+  void setMethodCallHandler(Future<void> Function(MethodCall call) handler) {
+    incoming = handler;
+  }
+
+  @override
+  void setMockMethodCallHandler(Future<void> Function(MethodCall call) handler)  => throw UnimplementedError();
+
+  void validateOutgoingMethodCalls(List<MethodCall> calls) {
+    expect(outgoingCalls.length, calls.length);
+    bool hasError = false;
+    for (int i = 0; i < calls.length; i++) {
+      final ByteData outgoingData = codec.encodeMethodCall(outgoingCalls[i]);
+      final ByteData expectedData = codec.encodeMethodCall(calls[i]);
+      final String outgoingString = utf8.decode(outgoingData.buffer.asUint8List());
+      final String expectedString = utf8.decode(expectedData.buffer.asUint8List());
+
+      if (outgoingString != expectedString) {
+        print(
+          'Index $i did not match:\n'
+          '  actual:   ${outgoingCalls[i]}\n'
+          '  expected: ${calls[i]}');
+        hasError = true;
+      }
+    }
+    if (hasError) {
+      fail('Calls did not match.');
+    }
+  }
+}
diff --git a/packages/flutter/test/services/text_input_test.dart b/packages/flutter/test/services/text_input_test.dart
index 380b909..cb6020f 100644
--- a/packages/flutter/test/services/text_input_test.dart
+++ b/packages/flutter/test/services/text_input_test.dart
@@ -201,6 +201,9 @@
   TextEditingValue currentTextEditingValue;
 
   @override
+  AutofillScope get currentAutofillScope => null;
+
+  @override
   void performAction(TextInputAction action) {
     latestMethodCall = 'performAction';
   }
diff --git a/packages/flutter/test/widgets/autofill_group_test.dart b/packages/flutter/test/widgets/autofill_group_test.dart
new file mode 100644
index 0000000..282edc5
--- /dev/null
+++ b/packages/flutter/test/widgets/autofill_group_test.dart
@@ -0,0 +1,166 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  testWidgets('AutofillGroup has the right clients', (WidgetTester tester) async {
+    const Key outerKey = Key('outer');
+    const Key innerKey = Key('inner');
+
+    const TextField client1 = TextField(autofillHints: <String>['1']);
+    const TextField client2 = TextField(autofillHints: <String>['2']);
+
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: AutofillGroup(
+            key: outerKey,
+            child: Column(children: <Widget>[
+              client1,
+              AutofillGroup(
+                key: innerKey,
+                child: Column(children: const <Widget>[client2, TextField()]),
+              ),
+            ]),
+          ),
+        ),
+      ),
+    );
+
+    final AutofillGroupState innerState = tester.state<AutofillGroupState>(find.byKey(innerKey));
+    final AutofillGroupState outerState = tester.state<AutofillGroupState>(find.byKey(outerKey));
+
+    final EditableTextState clientState1 = tester.state<EditableTextState>(
+      find.descendant(of: find.byWidget(client1), matching: find.byType(EditableText)),
+    );
+    final EditableTextState clientState2 = tester.state<EditableTextState>(
+      find.descendant(of: find.byWidget(client2), matching: find.byType(EditableText)),
+    );
+
+    expect(outerState.autofillClients, <EditableTextState>[clientState1]);
+    expect(innerState.autofillClients, <EditableTextState>[clientState2]);
+  });
+
+  testWidgets('new clients can be added & removed to a scope', (WidgetTester tester) async {
+    const Key scopeKey = Key('scope');
+
+    final List<String> hints = <String>[];
+
+    const TextField client1 = TextField(autofillHints: <String>['1']);
+    final TextField client2 = TextField(autofillHints: hints);
+
+    StateSetter setState;
+
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: AutofillGroup(
+            key: scopeKey,
+            child: StatefulBuilder(
+              builder: (BuildContext context, StateSetter setter) {
+                setState = setter;
+                return Column(children: <Widget>[client1, client2]);
+              },
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final AutofillGroupState scopeState = tester.state<AutofillGroupState>(find.byKey(scopeKey));
+
+    final EditableTextState clientState1 = tester.state<EditableTextState>(
+      find.descendant(of: find.byWidget(client1), matching: find.byType(EditableText)),
+    );
+    final EditableTextState clientState2 = tester.state<EditableTextState>(
+      find.descendant(of: find.byWidget(client2), matching: find.byType(EditableText)),
+    );
+
+    expect(scopeState.autofillClients, <EditableTextState>[clientState1]);
+
+    // Add to scope.
+    setState(() { hints.add('2'); });
+
+    await tester.pump();
+
+    expect(scopeState.autofillClients.length, 2);
+    expect(scopeState.autofillClients, contains(clientState1));
+    expect(scopeState.autofillClients, contains(clientState2));
+
+    // Remove from scope again.
+    setState(() { hints.clear(); });
+
+    await tester.pump();
+
+    expect(scopeState.autofillClients, <EditableTextState>[clientState1]);
+  });
+
+  testWidgets('AutofillGroup has the right clients after reparenting', (WidgetTester tester) async {
+    const Key outerKey = Key('outer');
+    const Key innerKey = Key('inner');
+    final GlobalKey keyClient3 = GlobalKey();
+
+    const TextField client1 = TextField(autofillHints: <String>['1']);
+    const TextField client2 = TextField(autofillHints: <String>['2']);
+
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: AutofillGroup(
+            key: outerKey,
+            child: Column(children: <Widget>[
+              client1,
+              AutofillGroup(
+                key: innerKey,
+                child: Column(children: <Widget>[
+                  client2,
+                  TextField(key: keyClient3, autofillHints: const <String>['3']),
+                ]),
+              ),
+            ]),
+          ),
+        ),
+      ),
+    );
+
+    final AutofillGroupState innerState = tester.state<AutofillGroupState>(find.byKey(innerKey));
+    final AutofillGroupState outerState = tester.state<AutofillGroupState>(find.byKey(outerKey));
+
+    final EditableTextState clientState1 = tester.state<EditableTextState>(
+      find.descendant(of: find.byWidget(client1), matching: find.byType(EditableText)),
+    );
+    final EditableTextState clientState2 = tester.state<EditableTextState>(
+      find.descendant(of: find.byWidget(client2), matching: find.byType(EditableText)),
+    );
+
+    final EditableTextState clientState3 = tester.state<EditableTextState>(
+      find.descendant(of: find.byKey(keyClient3), matching: find.byType(EditableText)),
+    );
+
+    await tester.pumpWidget(
+      MaterialApp(
+        home: Scaffold(
+          body: AutofillGroup(
+            key: outerKey,
+            child: Column(children: <Widget>[
+              client1,
+              TextField(key: keyClient3, autofillHints: const <String>['3']),
+              AutofillGroup(
+                key: innerKey,
+                child: Column(children: const <Widget>[client2]),
+              ),
+            ]),
+          ),
+        ),
+      ),
+    );
+
+    expect(outerState.autofillClients.length, 2);
+    expect(outerState.autofillClients, contains(clientState1));
+    expect(outerState.autofillClients, contains(clientState3));
+    expect(innerState.autofillClients, <EditableTextState>[clientState2]);
+  });
+}
diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart
index aeacc0e..69457bb 100644
--- a/packages/flutter/test/widgets/editable_text_test.dart
+++ b/packages/flutter/test/widgets/editable_text_test.dart
@@ -4116,8 +4116,17 @@
 
     await tester.showKeyboard(find.byType(EditableText));
     // TextInput.show should be before TextInput.setEditingState
-    final List<String> logOrder = <String>['TextInput.setClient', 'TextInput.show', 'TextInput.setEditableSizeAndTransform', 'TextInput.setStyle', 'TextInput.setEditingState', 'TextInput.setEditingState', 'TextInput.show'];
-    expect(tester.testTextInput.log.length, 7);
+    final List<String> logOrder = <String>[
+      'TextInput.setClient',
+      'TextInput.show',
+      'TextInput.setEditableSizeAndTransform',
+      'TextInput.requestAutofill',
+      'TextInput.setStyle',
+      'TextInput.setEditingState',
+      'TextInput.setEditingState',
+      'TextInput.show',
+    ];
+    expect(tester.testTextInput.log.length, 8);
     int index = 0;
     for (final MethodCall m in tester.testTextInput.log) {
       expect(m.method, logOrder[index]);
@@ -4156,6 +4165,7 @@
       'TextInput.setClient',
       'TextInput.show',
       'TextInput.setEditableSizeAndTransform',
+      'TextInput.requestAutofill',
       'TextInput.setStyle',
       'TextInput.setEditingState',
       'TextInput.setEditingState',
@@ -4203,6 +4213,7 @@
       'TextInput.setClient',
       'TextInput.show',
       'TextInput.setEditableSizeAndTransform',
+      'TextInput.requestAutofill',
       'TextInput.setStyle',
       'TextInput.setEditingState',
       'TextInput.setEditingState',