improve implementation of Oreo autofill (#9), now supporting all Android/W3cHints, using all Keepass fields (if hints match field name). Make hint comparison code clearer and always compare case insensitive

This commit is contained in:
Philipp Crocoll
2017-12-29 07:07:04 +01:00
parent fb018946b9
commit c8d56a237b
7 changed files with 289 additions and 166 deletions

View File

@@ -14,8 +14,8 @@ namespace keepass2android.services.AutofillBase
public class AutofillFieldMetadata public class AutofillFieldMetadata
{ {
public SaveDataType SaveType { get; set; } public SaveDataType SaveType { get; set; }
public string[] AutofillHints { get; set; } public string[] AutofillCanonicalHints { get; set; }
public AutofillId AutofillId { get; } public AutofillId AutofillId { get; }
public AutofillType AutofillType { get; } public AutofillType AutofillType { get; }
@@ -29,14 +29,14 @@ namespace keepass2android.services.AutofillBase
AutofillOptions = view.GetAutofillOptions(); AutofillOptions = view.GetAutofillOptions();
Focused = view.IsFocused; Focused = view.IsFocused;
var supportedHints = AutofillHintsHelper.FilterForSupportedHints(view.GetAutofillHints()); var supportedHints = AutofillHintsHelper.FilterForSupportedHints(view.GetAutofillHints());
var storedHints = AutofillHintsHelper.ConvertToStoredHints(supportedHints); var canonicalHints = AutofillHintsHelper.ConvertToCanonicalHints(supportedHints);
SetHints(storedHints.ToArray()); SetHints(canonicalHints.ToArray());
} }
void SetHints(string[] value) void SetHints(string[] value)
{ {
AutofillHints = value; AutofillCanonicalHints = value;
UpdateSaveTypeFromHints(); UpdateSaveTypeFromHints();
} }
@@ -62,11 +62,11 @@ namespace keepass2android.services.AutofillBase
{ {
//TODO future add savetypes for W3cHints //TODO future add savetypes for W3cHints
SaveType = 0; SaveType = 0;
if (AutofillHints == null) if (AutofillCanonicalHints == null)
{ {
return; return;
} }
foreach (var hint in AutofillHints) foreach (var hint in AutofillCanonicalHints)
{ {
switch (hint) switch (hint)
{ {

View File

@@ -12,17 +12,19 @@ namespace keepass2android.services.AutofillBase
public class AutofillFieldMetadataCollection public class AutofillFieldMetadataCollection
{ {
List<AutofillId> AutofillIds = new List<AutofillId>(); List<AutofillId> AutofillIds = new List<AutofillId>();
Dictionary<string, List<AutofillFieldMetadata>> AutofillHintsToFieldsMap = new Dictionary<string, List<AutofillFieldMetadata>>();
public List<string> AllAutofillHints { get; } Dictionary<string, List<AutofillFieldMetadata>> AutofillCanonicalHintsToFieldsMap = new Dictionary<string, List<AutofillFieldMetadata>>();
public List<string> FocusedAutofillHints { get; }
public List<string> AllAutofillCanonicalHints { get; }
public List<string> FocusedAutofillCanonicalHints { get; }
int Size = 0; int Size = 0;
public SaveDataType SaveType { get; set; } public SaveDataType SaveType { get; set; }
public AutofillFieldMetadataCollection() public AutofillFieldMetadataCollection()
{ {
SaveType = 0; SaveType = 0;
FocusedAutofillHints = new List<string>(); FocusedAutofillCanonicalHints = new List<string>();
AllAutofillHints = new List<string>(); AllAutofillCanonicalHints = new List<string>();
} }
public void Add(AutofillFieldMetadata autofillFieldMetadata) public void Add(AutofillFieldMetadata autofillFieldMetadata)
@@ -30,19 +32,19 @@ namespace keepass2android.services.AutofillBase
SaveType |= autofillFieldMetadata.SaveType; SaveType |= autofillFieldMetadata.SaveType;
Size++; Size++;
AutofillIds.Add(autofillFieldMetadata.AutofillId); AutofillIds.Add(autofillFieldMetadata.AutofillId);
var hintsList = autofillFieldMetadata.AutofillHints; var hintsList = autofillFieldMetadata.AutofillCanonicalHints;
AllAutofillHints.AddRange(hintsList); AllAutofillCanonicalHints.AddRange(hintsList);
if (autofillFieldMetadata.Focused) if (autofillFieldMetadata.Focused)
{ {
FocusedAutofillHints.AddRange(hintsList); FocusedAutofillCanonicalHints.AddRange(hintsList);
} }
foreach (var hint in autofillFieldMetadata.AutofillHints) foreach (var hint in autofillFieldMetadata.AutofillCanonicalHints)
{ {
if (!AutofillHintsToFieldsMap.ContainsKey(hint)) if (!AutofillCanonicalHintsToFieldsMap.ContainsKey(hint))
{ {
AutofillHintsToFieldsMap.Add(hint, new List<AutofillFieldMetadata>()); AutofillCanonicalHintsToFieldsMap.Add(hint, new List<AutofillFieldMetadata>());
} }
AutofillHintsToFieldsMap[hint].Add(autofillFieldMetadata); AutofillCanonicalHintsToFieldsMap[hint].Add(autofillFieldMetadata);
} }
} }
@@ -51,9 +53,17 @@ namespace keepass2android.services.AutofillBase
return AutofillIds.ToArray(); return AutofillIds.ToArray();
} }
public List<AutofillFieldMetadata> GetFieldsForHint(String hint) /// <summary>
/// returns the fields for the given hint or an empty list.
/// </summary>
public List<AutofillFieldMetadata> GetFieldsForHint(String canonicalHint)
{ {
return AutofillHintsToFieldsMap[hint]; List<AutofillFieldMetadata> result;
if (!AutofillCanonicalHintsToFieldsMap.TryGetValue(canonicalHint, out result))
{
result = new List<AutofillFieldMetadata>();
}
return result;
} }

View File

@@ -16,16 +16,76 @@ namespace keepass2android.services.AutofillBase
{ {
class AutofillHintsHelper class AutofillHintsHelper
{ {
private static readonly HashSet<string> validHints = new HashSet<string>() private static readonly HashSet<string> _allSupportedHints = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
View.AutofillHintUsername, View.AutofillHintCreditCardExpirationDate,
View.AutofillHintCreditCardExpirationDay,
View.AutofillHintCreditCardExpirationMonth,
View.AutofillHintCreditCardExpirationYear,
View.AutofillHintCreditCardNumber,
View.AutofillHintCreditCardSecurityCode,
View.AutofillHintEmailAddress,
View.AutofillHintPhone,
View.AutofillHintName,
View.AutofillHintPassword, View.AutofillHintPassword,
View.AutofillHintPostalAddress,
View.AutofillHintPostalCode,
View.AutofillHintUsername,
W3cHints.HONORIFIC_PREFIX,
W3cHints.NAME,
W3cHints.GIVEN_NAME,
W3cHints.ADDITIONAL_NAME,
W3cHints.FAMILY_NAME,
W3cHints.HONORIFIC_SUFFIX,
W3cHints.USERNAME, W3cHints.USERNAME,
W3cHints.NEW_PASSWORD,
W3cHints.CURRENT_PASSWORD, W3cHints.CURRENT_PASSWORD,
W3cHints.NEW_PASSWORD W3cHints.ORGANIZATION_TITLE,
W3cHints.ORGANIZATION,
W3cHints.STREET_ADDRESS,
W3cHints.ADDRESS_LINE1,
W3cHints.ADDRESS_LINE2,
W3cHints.ADDRESS_LINE3,
W3cHints.ADDRESS_LEVEL4,
W3cHints.ADDRESS_LEVEL3,
W3cHints.ADDRESS_LEVEL2,
W3cHints.ADDRESS_LEVEL1,
W3cHints.COUNTRY,
W3cHints.COUNTRY_NAME,
W3cHints.POSTAL_CODE,
W3cHints.CC_NAME,
W3cHints.CC_GIVEN_NAME,
W3cHints.CC_ADDITIONAL_NAME,
W3cHints.CC_FAMILY_NAME,
W3cHints.CC_NUMBER,
W3cHints.CC_EXPIRATION,
W3cHints.CC_EXPIRATION_MONTH,
W3cHints.CC_EXPIRATION_YEAR,
W3cHints.CC_CSC,
W3cHints.CC_TYPE,
W3cHints.TRANSACTION_CURRENCY,
W3cHints.TRANSACTION_AMOUNT,
W3cHints.LANGUAGE,
W3cHints.BDAY,
W3cHints.BDAY_DAY,
W3cHints.BDAY_MONTH,
W3cHints.BDAY_YEAR,
W3cHints.SEX,
W3cHints.URL,
W3cHints.PHOTO,
W3cHints.TEL,
W3cHints.TEL_COUNTRY_CODE,
W3cHints.TEL_NATIONAL,
W3cHints.TEL_AREA_CODE,
W3cHints.TEL_LOCAL,
W3cHints.TEL_LOCAL_PREFIX,
W3cHints.TEL_LOCAL_SUFFIX,
W3cHints.TEL_EXTENSION,
W3cHints.EMAIL,
W3cHints.IMPP,
}; };
private static readonly Dictionary<string, string> hintReplacements= new Dictionary<string, string>() private static readonly Dictionary<string, string> hintToCanonicalReplacement= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
{W3cHints.EMAIL, View.AutofillHintEmailAddress}, {W3cHints.EMAIL, View.AutofillHintEmailAddress},
{W3cHints.USERNAME, View.AutofillHintUsername}, {W3cHints.USERNAME, View.AutofillHintUsername},
@@ -41,9 +101,9 @@ namespace keepass2android.services.AutofillBase
}; };
public static bool IsValidHint(string hint) public static bool IsSupportedHint(string hint)
{ {
return validHints.Contains(hint); return _allSupportedHints.Contains(hint);
} }
@@ -53,7 +113,7 @@ namespace keepass2android.services.AutofillBase
int i = 0; int i = 0;
foreach (var hint in hints) foreach (var hint in hints)
{ {
if (IsValidHint(hint)) if (IsSupportedHint(hint))
{ {
filteredHints[i++] = hint; filteredHints[i++] = hint;
} }
@@ -69,16 +129,18 @@ namespace keepass2android.services.AutofillBase
public static List<string> ConvertToStoredHints(string[] supportedHints) /// <summary>
/// transforms hints by replacing some W3cHints by their Android counterparts and transforming everything to lowercase
/// </summary>
public static List<string> ConvertToCanonicalHints(string[] supportedHints)
{ {
List<string> result = new List<string>(); List<string> result = new List<string>();
foreach (string hint in supportedHints) foreach (string hint in supportedHints)
{ {
string storedHint = hint; string canonicalHint;
if (hintReplacements.ContainsKey(hint)) if (!hintToCanonicalReplacement.TryGetValue(hint, out canonicalHint))
storedHint = hintReplacements[hint]; canonicalHint = hint;
result.Add(storedHint); result.Add(canonicalHint.ToLower());
} }
return result; return result;

View File

@@ -1,25 +1,89 @@
using Android.App.Assist; using System.Collections.Generic;
using Android.App.Assist;
using Android.Views.Autofill; using Android.Views.Autofill;
namespace keepass2android.services.AutofillBase.model namespace keepass2android.services.AutofillBase.model
{ {
public class FilledAutofillField public class FilledAutofillField
{ {
public string TextValue { get; set; } private string[] _autofillHints;
public string TextValue { get; set; }
public long? DateValue { get; set; } public long? DateValue { get; set; }
public bool? ToggleValue { get; set; } public bool? ToggleValue { get; set; }
public string[] AutofillHints { get; set; }
/// <summary>
public FilledAutofillField() /// returns the autofill hints for the filled field. These are always lowercased for simpler string comparison.
/// </summary>
public string[] AutofillHints
{
get
{
return _autofillHints;
}
set
{
_autofillHints = value;
for (int i = 0; i < _autofillHints.Length; i++)
_autofillHints[i] = _autofillHints[i].ToLower();
}
}
public bool Protected { get; set; }
public FilledAutofillField()
{} {}
public FilledAutofillField(AssistStructure.ViewNode viewNode)
{
string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(viewNode.GetAutofillHints());
List<string> hintList = new List<string>();
string nextHint = null;
for (int i = 0; i < rawHints.Length; i++)
{
string hint = rawHints[i];
if (i < rawHints.Length - 1)
{
nextHint = rawHints[i + 1];
}
// First convert the compound W3C autofill hints
if (W3cHints.isW3cSectionPrefix(hint) && i < rawHints.Length - 1)
{
hint = rawHints[++i];
CommonUtil.logd($"Hint is a W3C section prefix; using {hint} instead");
if (i < rawHints.Length - 1)
{
nextHint = rawHints[i + 1];
}
}
if (W3cHints.isW3cTypePrefix(hint) && nextHint != null && W3cHints.isW3cTypeHint(nextHint))
{
hint = nextHint;
i++;
CommonUtil.logd($"Hint is a W3C type prefix; using {hint} instead");
}
if (W3cHints.isW3cAddressType(hint) && nextHint != null)
{
hint = nextHint;
i++;
CommonUtil.logd($"Hint is a W3C address prefix; using {hint} instead");
}
/*public FilledAutofillField(AssistStructure.ViewNode viewNode) // Then check if the "actual" hint is supported.
{ if (AutofillHintsHelper.IsSupportedHint(hint))
AutofillHintsHelper = AutofillHelper.FilterForSupportedHints(viewNode.GetAutofillHints()); {
hintList.Add(hint);
}
else
{
CommonUtil.loge($"Invalid hint: {rawHints[i]}");
}
}
AutofillHints = hintList.ToArray();
//TODO port updated FilledAutofillField? //TODO port updated FilledAutofillField
AutofillValue autofillValue = viewNode.AutofillValue; AutofillValue autofillValue = viewNode.AutofillValue;
if (autofillValue != null) if (autofillValue != null)
{ {
@@ -41,9 +105,9 @@ namespace keepass2android.services.AutofillBase.model
TextValue = autofillValue.TextValue; TextValue = autofillValue.TextValue;
} }
} }
}*/ }
public bool IsNull() public bool IsNull()
{ {
return TextValue == null && DateValue == null && ToggleValue == null; return TextValue == null && DateValue == null && ToggleValue == null;
} }

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using Android.Service.Autofill; using Android.Service.Autofill;
using Android.Util; using Android.Util;
using Android.Views; using Android.Views;
@@ -12,121 +13,48 @@ namespace keepass2android.services.AutofillBase.model
/// </summary> /// </summary>
public class FilledAutofillFieldCollection public class FilledAutofillFieldCollection
{ {
public Dictionary<string, FilledAutofillField> HintMap { get; set; } public Dictionary<string, FilledAutofillField> HintMap { get; }
public string DatasetName { get; set; } public string DatasetName { get; set; }
public FilledAutofillFieldCollection(Dictionary<string, FilledAutofillField> hintMap, string datasetName = "") public FilledAutofillFieldCollection(Dictionary<string, FilledAutofillField> hintMap, string datasetName = "")
{ {
HintMap = hintMap; //recreate hint map making sure we compare case insensitive
HintMap = BuildHintMap();
foreach (var p in hintMap)
HintMap.Add(p.Key, p.Value);
DatasetName = datasetName; DatasetName = datasetName;
} }
public FilledAutofillFieldCollection() : this(new Dictionary<string, FilledAutofillField>()) public FilledAutofillFieldCollection() : this(BuildHintMap())
{} {}
/// <summary> private static Dictionary<string, FilledAutofillField> BuildHintMap()
{
return new Dictionary<string, FilledAutofillField>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Adds a filledAutofillField to the collection, indexed by all of its hints. /// Adds a filledAutofillField to the collection, indexed by all of its hints.
/// </summary> /// </summary>
/// <returns>The add.</returns> /// <returns>The add.</returns>
/// <param name="filledAutofillField">Filled autofill field.</param> /// <param name="filledAutofillField">Filled autofill field.</param>
public void Add(FilledAutofillField filledAutofillField) public void Add(FilledAutofillField filledAutofillField)
{ {
string[] autofillHints = filledAutofillField.AutofillHints; foreach (string hint in filledAutofillField.AutofillHints)
{
string nextHint = null; if (AutofillHintsHelper.IsSupportedHint(hint))
for (int i = 0; i < autofillHints.Length; i++)
{
string hint = autofillHints[i];
if (i < autofillHints.Length - 1)
{
nextHint = autofillHints[i + 1];
}
// First convert the compound W3C autofill hints
if (isW3cSectionPrefix(hint) && i < autofillHints.Length - 1)
{
hint = autofillHints[++i];
CommonUtil.logd($"Hint is a W3C section prefix; using {hint} instead");
if (i < autofillHints.Length - 1)
{
nextHint = autofillHints[i + 1];
}
}
if (isW3cTypePrefix(hint) && nextHint != null && isW3cTypeHint(nextHint))
{
hint = nextHint;
i++;
CommonUtil.logd($"Hint is a W3C type prefix; using {hint} instead");
}
if (isW3cAddressType(hint) && nextHint != null)
{
hint = nextHint;
i++;
CommonUtil.logd($"Hint is a W3C address prefix; using {hint} instead");
}
// Then check if the "actual" hint is supported.
if (AutofillHintsHelper.IsValidHint(hint))
{ {
HintMap.Add(hint, filledAutofillField); HintMap.Add(hint, filledAutofillField);
} }
else else
{ {
CommonUtil.loge($"Invalid hint: {autofillHints[i]}"); CommonUtil.loge($"Invalid hint: {hint}");
} }
} }
} }
private static bool isW3cSectionPrefix(string hint)
{
return hint.StartsWith(W3cHints.PREFIX_SECTION);
}
private static bool isW3cAddressType(string hint)
{
switch (hint)
{
case W3cHints.SHIPPING:
case W3cHints.BILLING:
return true;
}
return false;
}
private static bool isW3cTypePrefix(string hint)
{
switch (hint)
{
case W3cHints.PREFIX_WORK:
case W3cHints.PREFIX_FAX:
case W3cHints.PREFIX_HOME:
case W3cHints.PREFIX_PAGER:
return true;
}
return false;
}
private static bool isW3cTypeHint(string hint)
{
switch (hint)
{
case W3cHints.TEL:
case W3cHints.TEL_COUNTRY_CODE:
case W3cHints.TEL_NATIONAL:
case W3cHints.TEL_AREA_CODE:
case W3cHints.TEL_LOCAL:
case W3cHints.TEL_LOCAL_PREFIX:
case W3cHints.TEL_LOCAL_SUFFIX:
case W3cHints.TEL_EXTENSION:
case W3cHints.EMAIL:
case W3cHints.IMPP:
return true;
}
Log.Warn(CommonUtil.Tag, "Invalid W3C type hint: " + hint);
return false;
}
/// <summary> /// <summary>
/// Populates a Dataset.Builder with appropriate values for each AutofillId /// Populates a Dataset.Builder with appropriate values for each AutofillId
/// in a AutofillFieldMetadataCollection. /// in a AutofillFieldMetadataCollection.
@@ -142,16 +70,10 @@ namespace keepass2android.services.AutofillBase.model
public bool ApplyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection, Dataset.Builder datasetBuilder) public bool ApplyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection, Dataset.Builder datasetBuilder)
{ {
bool setValueAtLeastOnce = false; bool setValueAtLeastOnce = false;
List<string> allHints = autofillFieldMetadataCollection.AllAutofillHints;
for (int hintIndex = 0; hintIndex < allHints.Count; hintIndex++) foreach (string hint in autofillFieldMetadataCollection.AllAutofillCanonicalHints)
{ {
string hint = allHints[hintIndex]; foreach (AutofillFieldMetadata autofillFieldMetadata in autofillFieldMetadataCollection.GetFieldsForHint(hint))
List<AutofillFieldMetadata> fillableAutofillFields = autofillFieldMetadataCollection.GetFieldsForHint(hint);
if (fillableAutofillFields == null)
{
continue;
}
foreach (AutofillFieldMetadata autofillFieldMetadata in fillableAutofillFields)
{ {
FilledAutofillField filledAutofillField; FilledAutofillField filledAutofillField;
if (!HintMap.TryGetValue(hint, out filledAutofillField) || (filledAutofillField == null)) if (!HintMap.TryGetValue(hint, out filledAutofillField) || (filledAutofillField == null))

View File

@@ -1,4 +1,6 @@
namespace keepass2android.services.AutofillBase.model using Android.Util;
namespace keepass2android.services.AutofillBase.model
{ {
public class W3cHints public class W3cHints
{ {
@@ -70,5 +72,56 @@
private W3cHints() private W3cHints()
{ {
} }
public static bool isW3cSectionPrefix(string hint)
{
return hint.ToLower().StartsWith(W3cHints.PREFIX_SECTION);
}
public static bool isW3cAddressType(string hint)
{
switch (hint.ToLower())
{
case W3cHints.SHIPPING:
case W3cHints.BILLING:
return true;
}
return false;
}
public static bool isW3cTypePrefix(string hint)
{
switch (hint.ToLower())
{
case W3cHints.PREFIX_WORK:
case W3cHints.PREFIX_FAX:
case W3cHints.PREFIX_HOME:
case W3cHints.PREFIX_PAGER:
return true;
}
return false;
}
public static bool isW3cTypeHint(string hint)
{
switch (hint.ToLower())
{
case W3cHints.TEL:
case W3cHints.TEL_COUNTRY_CODE:
case W3cHints.TEL_NATIONAL:
case W3cHints.TEL_AREA_CODE:
case W3cHints.TEL_LOCAL:
case W3cHints.TEL_LOCAL_PREFIX:
case W3cHints.TEL_LOCAL_SUFFIX:
case W3cHints.TEL_EXTENSION:
case W3cHints.EMAIL:
case W3cHints.IMPP:
return true;
}
Log.Warn(CommonUtil.Tag, "Invalid W3C type hint: " + hint);
return false;
}
} }
} }

View File

@@ -41,32 +41,44 @@ namespace keepass2android.services.Kp2aAutofill
if (!App.Kp2a.GetDb().Loaded || (App.Kp2a.QuickLocked)) if (!App.Kp2a.GetDb().Loaded || (App.Kp2a.QuickLocked))
return null; return null;
string username = App.Kp2a.GetDb().LastOpenedEntry.Entry.Strings.ReadSafe(PwDefs.UserNameField);
string password = App.Kp2a.GetDb().LastOpenedEntry.Entry.Strings.ReadSafe(PwDefs.PasswordField);
FilledAutofillField pwdField =
new FilledAutofillField
{
AutofillHints = new[] {View.AutofillHintPassword},
TextValue = password
};
FilledAutofillField userField = new FilledAutofillField
{
AutofillHints = new[] {View.AutofillHintUsername},
TextValue = username
};
FilledAutofillFieldCollection fieldCollection = new FilledAutofillFieldCollection(); FilledAutofillFieldCollection fieldCollection = new FilledAutofillFieldCollection();
fieldCollection.HintMap = new Dictionary<string, FilledAutofillField>();
fieldCollection.Add(userField);
fieldCollection.Add(pwdField);
fieldCollection.DatasetName = App.Kp2a.GetDb().LastOpenedEntry.Entry.Strings.ReadSafe(PwDefs.TitleField); var pwEntry = App.Kp2a.GetDb().LastOpenedEntry.Entry;
foreach (string key in pwEntry.Strings.GetKeys())
{
FilledAutofillField field =
new FilledAutofillField
{
AutofillHints = new[] { GetCanonicalHintFromKp2aField(pwEntry, key) },
TextValue = pwEntry.Strings.ReadSafe(key),
Protected = pwEntry.Strings.Get(key).IsProtected
};
fieldCollection.Add(field);
}
//TODO add support for Keepass templates
//TODO add values like expiration?
//TODO if cc-exp is there, also set cc-exp-month etc.
fieldCollection.DatasetName = pwEntry.Strings.ReadSafe(PwDefs.TitleField);
return fieldCollection; return fieldCollection;
} }
private static readonly Dictionary<string, string> keyToHint = new Dictionary<string, string>()
{
{PwDefs.UserNameField, View.AutofillHintUsername },
{PwDefs.PasswordField, View.AutofillHintPassword },
{PwDefs.UrlField, W3cHints.URL },
};
private string GetCanonicalHintFromKp2aField(PwEntry pwEntry, string key)
{
if (!keyToHint.TryGetValue(key, out string result))
result = key;
result = result.ToLower();
return result;
}
public override IAutofillIntentBuilder IntentBuilder => new Kp2aAutofillIntentBuilder(); public override IAutofillIntentBuilder IntentBuilder => new Kp2aAutofillIntentBuilder();
} }