From 0e020924ff4a749085daaa64c422fa2f38f23d84 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 14 Nov 2017 23:13:55 -0500 Subject: [PATCH] refactor autofill classes. basic login support. --- src/Android/Android.csproj | 16 ++-- .../AutofillFieldMetadataCollection.cs | 51 ------------ .../Autofill/AutofillFrameworkService.cs | 80 ------------------ .../{AutofillHelper.cs => AutofillHelpers.cs} | 67 ++++++--------- src/Android/Autofill/AutofillService.cs | 83 +++++++++++++++++++ src/Android/Autofill/CipherFilledItem.cs | 71 ++++++++++++++++ .../{AutofillFieldMetadata.cs => Field.cs} | 22 ++--- src/Android/Autofill/FieldCollection.cs | 53 ++++++++++++ ...{FilledAutofillField.cs => FilledField.cs} | 69 ++++++++------- ...Collection.cs => FilledFieldCollection.cs} | 79 +++++++++--------- src/Android/Autofill/IFilledItem.cs | 12 +++ .../{StructureParser.cs => Parser.cs} | 32 +++++-- 12 files changed, 368 insertions(+), 267 deletions(-) delete mode 100644 src/Android/Autofill/AutofillFieldMetadataCollection.cs delete mode 100644 src/Android/Autofill/AutofillFrameworkService.cs rename src/Android/Autofill/{AutofillHelper.cs => AutofillHelpers.cs} (56%) create mode 100644 src/Android/Autofill/AutofillService.cs create mode 100644 src/Android/Autofill/CipherFilledItem.cs rename src/Android/Autofill/{AutofillFieldMetadata.cs => Field.cs} (84%) create mode 100644 src/Android/Autofill/FieldCollection.cs rename src/Android/Autofill/{FilledAutofillField.cs => FilledField.cs} (50%) rename src/Android/Autofill/{FilledAutofillFieldCollection.cs => FilledFieldCollection.cs} (62%) create mode 100644 src/Android/Autofill/IFilledItem.cs rename src/Android/Autofill/{StructureParser.cs => Parser.cs} (66%) diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index cab965c6c..ecaa06b23 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -287,10 +287,12 @@ - - - - + + + + + + @@ -298,8 +300,8 @@ - - + + @@ -328,7 +330,7 @@ - + diff --git a/src/Android/Autofill/AutofillFieldMetadataCollection.cs b/src/Android/Autofill/AutofillFieldMetadataCollection.cs deleted file mode 100644 index 449a3f0d3..000000000 --- a/src/Android/Autofill/AutofillFieldMetadataCollection.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections.Generic; -using Android.Service.Autofill; -using Android.Views; -using Android.Views.Autofill; - -namespace Bit.Android.Autofill -{ - public class AutofillFieldMetadataCollection - { - private int _size = 0; - - public List Ids { get; private set; } = new List(); - public List AutofillIds { get; private set; } = new List(); - public SaveDataType SaveType { get; private set; } = SaveDataType.Generic; - public List AutofillHints { get; private set; } = new List(); - public List FocusedAutofillHints { get; private set; } = new List(); - public List Feilds { get; private set; } - public IDictionary IdToFieldMap { get; private set; } = - new Dictionary(); - public IDictionary> AutofillHintsToFieldsMap { get; private set; } = - new Dictionary>(); - - public void Add(AutofillFieldMetadata data) - { - _size++; - SaveType |= data.SaveType; - Ids.Add(data.Id); - AutofillIds.Add(data.AutofillId); - IdToFieldMap.Add(data.Id, data); - - if((data.AutofillHints?.Count ?? 0) > 0) - { - AutofillHints.AddRange(data.AutofillHints); - if(data.IsFocused) - { - FocusedAutofillHints.AddRange(data.AutofillHints); - } - - foreach(var hint in data.AutofillHints) - { - if(!AutofillHintsToFieldsMap.ContainsKey(hint)) - { - AutofillHintsToFieldsMap.Add(hint, new List()); - } - - AutofillHintsToFieldsMap[hint].Add(data); - } - } - } - } -} \ No newline at end of file diff --git a/src/Android/Autofill/AutofillFrameworkService.cs b/src/Android/Autofill/AutofillFrameworkService.cs deleted file mode 100644 index 123b85fe4..000000000 --- a/src/Android/Autofill/AutofillFrameworkService.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Android; -using Android.App; -using Android.OS; -using Android.Runtime; -using Android.Service.Autofill; -using Android.Views; -using System.Collections.Generic; -using System.Linq; - -namespace Bit.Android.Autofill -{ - [Service(Permission = Manifest.Permission.BindAutofillService, Label = "bitwarden")] - [IntentFilter(new string[] { "android.service.autofill.AutofillService" })] - [MetaData("android.autofill", Resource = "@xml/autofillservice")] - [Register("com.x8bit.bitwarden.Autofill.AutofillFrameworkService")] - public class AutofillFrameworkService : global::Android.Service.Autofill.AutofillService - { - public override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) - { - var structure = request.FillContexts?.LastOrDefault()?.Structure; - if(structure == null) - { - return; - } - - var clientState = request.ClientState; - - var parser = new StructureParser(structure); - parser.ParseForFill(); - - // build response - var responseBuilder = new FillResponse.Builder(); - - var username1 = new FilledAutofillField { TextValue = "username1" }; - var password1 = new FilledAutofillField { TextValue = "pass1" }; - var login1 = new Dictionary - { - { View.AutofillHintUsername, username1 }, - { View.AutofillHintPassword, password1 } - }; - var coll = new FilledAutofillFieldCollection("Login 1 Name", login1); - - var username2 = new FilledAutofillField { TextValue = "username2" }; - var password2 = new FilledAutofillField { TextValue = "pass2" }; - var login2 = new Dictionary - { - { View.AutofillHintUsername, username2 }, - { View.AutofillHintPassword, password2 } - }; - var col2 = new FilledAutofillFieldCollection("Login 2 Name", login2); - - var clientFormDataMap = new Dictionary - { - { "login-1-guid", coll }, - { "login-2-guid", col2 } - }; - - var response = AutofillHelper.NewResponse(this, false, parser.AutofillFields, clientFormDataMap); - // end build response - - callback.OnSuccess(response); - } - - public override void OnSaveRequest(SaveRequest request, SaveCallback callback) - { - var structure = request.FillContexts?.LastOrDefault()?.Structure; - if(structure == null) - { - return; - } - - var clientState = request.ClientState; - - var parser = new StructureParser(structure); - parser.ParseForSave(); - var filledAutofillFieldCollection = parser.GetClientFormData(); - //SaveFilledAutofillFieldCollection(filledAutofillFieldCollection); - } - } -} diff --git a/src/Android/Autofill/AutofillHelper.cs b/src/Android/Autofill/AutofillHelpers.cs similarity index 56% rename from src/Android/Autofill/AutofillHelper.cs rename to src/Android/Autofill/AutofillHelpers.cs index bfc5e7313..0b5c6b204 100644 --- a/src/Android/Autofill/AutofillHelper.cs +++ b/src/Android/Autofill/AutofillHelpers.cs @@ -4,37 +4,38 @@ using Android.Content; using Android.Service.Autofill; using Android.Views; using Android.Widget; +using System.Diagnostics; +using System.Linq; namespace Bit.Android.Autofill { - public static class AutofillHelper + public static class AutofillHelpers { /** * Wraps autofill data in a LoginCredential Dataset object which can then be sent back to the * client View. */ - public static Dataset NewDataset(Context context, AutofillFieldMetadataCollection autofillFields, - FilledAutofillFieldCollection filledAutofillFieldCollection, bool datasetAuth) + public static Dataset NewDataset(Context context, FieldCollection fields, IFilledItem filledItem, bool auth) { - var datasetName = filledAutofillFieldCollection.DatasetName; - if(datasetName != null) + var itemName = filledItem.Name; + if(itemName != null) { Dataset.Builder datasetBuilder; - if(datasetAuth) + if(auth) { datasetBuilder = new Dataset.Builder( - NewRemoteViews(context.PackageName, datasetName, "username", Resource.Drawable.fa_lock)); + NewRemoteViews(context.PackageName, itemName, filledItem.Subtitle, Resource.Drawable.fa_lock)); //IntentSender sender = AuthActivity.getAuthIntentSenderForDataset(context, datasetName); //datasetBuilder.SetAuthentication(sender); } else { datasetBuilder = new Dataset.Builder( - NewRemoteViews(context.PackageName, datasetName, "username", Resource.Drawable.user)); + NewRemoteViews(context.PackageName, itemName, filledItem.Subtitle, Resource.Drawable.user)); } - var setValueAtLeastOnce = filledAutofillFieldCollection.ApplyToFields(autofillFields, datasetBuilder); - if(setValueAtLeastOnce) + var setValue = filledItem.ApplyToFields(fields, datasetBuilder); + if(setValue) { return datasetBuilder.Build(); } @@ -56,59 +57,43 @@ namespace Bit.Android.Autofill * Wraps autofill data in a Response object (essentially a series of Datasets) which can then * be sent back to the client View. */ - public static FillResponse NewResponse(Context context, bool datasetAuth, - AutofillFieldMetadataCollection autofillFields, - IDictionary clientFormDataMap) + public static FillResponse NewResponse(Context context, bool auth, FieldCollection fields, + IDictionary items) { var responseBuilder = new FillResponse.Builder(); - if(clientFormDataMap != null) + if(items != null) { - foreach(var datasetName in clientFormDataMap.Keys) + foreach(var datasetName in items.Keys) { - if(clientFormDataMap.ContainsKey(datasetName)) + var dataset = NewDataset(context, fields, items[datasetName], auth); + if(dataset != null) { - var dataset = NewDataset(context, autofillFields, clientFormDataMap[datasetName], datasetAuth); - if(dataset != null) - { - responseBuilder.AddDataset(dataset); - } + responseBuilder.AddDataset(dataset); } } } - if(autofillFields.SaveType != SaveDataType.Generic) + if(true || fields.SaveType != SaveDataType.Generic) { - responseBuilder.SetSaveInfo( - new SaveInfo.Builder(autofillFields.SaveType, autofillFields.AutofillIds.ToArray()).Build()); + var info = new SaveInfo.Builder(fields.SaveType, fields.AutofillIds.ToArray()).Build(); + responseBuilder.SetSaveInfo(info); return responseBuilder.Build(); } else { - //Log.d(TAG, "These fields are not meant to be saved by autofill."); + Debug.WriteLine("These fields are not meant to be saved by autofill."); return null; } } - public static string[] FilterForSupportedHints(string[] hints) + public static List FilterForSupportedHints(string[] hints) { - if((hints?.Length ?? 0) == 0) + if(hints == null) { - return new string[0]; + return new List(); } - var filteredHints = new string[hints.Length]; - var i = 0; - foreach(var hint in hints) - { - if(IsValidHint(hint)) - { - filteredHints[i++] = hint; - } - } - - var finalFilteredHints = new string[i]; - Array.Copy(filteredHints, 0, finalFilteredHints, 0, i); - return finalFilteredHints; + return hints.Where(h => IsValidHint(h)).ToList(); } public static bool IsValidHint(string hint) diff --git a/src/Android/Autofill/AutofillService.cs b/src/Android/Autofill/AutofillService.cs new file mode 100644 index 000000000..53787ec16 --- /dev/null +++ b/src/Android/Autofill/AutofillService.cs @@ -0,0 +1,83 @@ +using Android; +using Android.App; +using Android.OS; +using Android.Runtime; +using Android.Service.Autofill; +using Android.Views; +using Bit.App.Abstractions; +using System.Collections.Generic; +using System.Linq; +using XLabs.Ioc; + +namespace Bit.Android.Autofill +{ + [Service(Permission = Manifest.Permission.BindAutofillService, Label = "bitwarden")] + [IntentFilter(new string[] { "android.service.autofill.AutofillService" })] + [MetaData("android.autofill", Resource = "@xml/autofillservice")] + [Register("com.x8bit.bitwarden.Autofill.AutofillService")] + public class AutofillService : global::Android.Service.Autofill.AutofillService + { + private ICipherService _cipherService; + + public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) + { + var structure = request.FillContexts?.LastOrDefault()?.Structure; + if(structure == null) + { + return; + } + + var clientState = request.ClientState; + + var parser = new Parser(structure); + parser.ParseForFill(); + + if(!parser.FieldCollection.Fields.Any() || string.IsNullOrWhiteSpace(parser.Uri)) + { + return; + } + + if(_cipherService == null) + { + _cipherService = Resolver.Resolve(); + } + + // build response + var items = new Dictionary(); + var ciphers = await _cipherService.GetAllAsync(parser.Uri); + if(ciphers.Item1.Any() || ciphers.Item2.Any()) + { + var allCiphers = ciphers.Item1.ToList(); + allCiphers.AddRange(ciphers.Item2.ToList()); + foreach(var cipher in allCiphers) + { + items.Add(cipher.Id, new CipherFilledItem(cipher)); + } + } + + if(!items.Any()) + { + return; + } + + var response = AutofillHelpers.NewResponse(this, false, parser.FieldCollection, items); + callback.OnSuccess(response); + } + + public override void OnSaveRequest(SaveRequest request, SaveCallback callback) + { + var structure = request.FillContexts?.LastOrDefault()?.Structure; + if(structure == null) + { + return; + } + + var clientState = request.ClientState; + + var parser = new Parser(structure); + parser.ParseForSave(); + var filledAutofillFieldCollection = parser.GetClientFormData(); + //SaveFilledAutofillFieldCollection(filledAutofillFieldCollection); + } + } +} diff --git a/src/Android/Autofill/CipherFilledItem.cs b/src/Android/Autofill/CipherFilledItem.cs new file mode 100644 index 000000000..2af844f38 --- /dev/null +++ b/src/Android/Autofill/CipherFilledItem.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using Android.Service.Autofill; +using Android.Views; +using Android.Views.Autofill; +using System.Linq; +using Android.Text; +using Bit.App.Models; +using Bit.App.Enums; + +namespace Bit.Android.Autofill +{ + public class CipherFilledItem : IFilledItem + { + private readonly Cipher _cipher; + + public CipherFilledItem(Cipher cipher) + { + _cipher = cipher; + Name = cipher.Name?.Decrypt() ?? "--"; + + switch(cipher.Type) + { + case CipherType.Login: + Subtitle = _cipher.Login.Username?.Decrypt() ?? string.Empty; + break; + default: + break; + } + } + + public string Name { get; set; } + public string Subtitle { get; set; } = string.Empty; + + public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder) + { + if(_cipher.Type == CipherType.Login && _cipher.Login != null) + { + var passwordField = fieldCollection.Fields.FirstOrDefault( + f => f.InputType.HasFlag(InputTypes.TextVariationPassword)); + if(passwordField == null) + { + return false; + } + + var password = _cipher.Login.Password?.Decrypt(); + if(string.IsNullOrWhiteSpace(password)) + { + return false; + } + + datasetBuilder.SetValue(passwordField.AutofillId, AutofillValue.ForText(password)); + + var usernameField = fieldCollection.Fields.TakeWhile(f => f.Id != passwordField.Id).LastOrDefault(); + if(usernameField != null) + { + if(!string.IsNullOrWhiteSpace(Subtitle)) + { + datasetBuilder.SetValue(usernameField.AutofillId, AutofillValue.ForText(Subtitle)); + } + } + + return true; + } + else + { + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Android/Autofill/AutofillFieldMetadata.cs b/src/Android/Autofill/Field.cs similarity index 84% rename from src/Android/Autofill/AutofillFieldMetadata.cs rename to src/Android/Autofill/Field.cs index 1eb9294a4..f8b8dd8e4 100644 --- a/src/Android/Autofill/AutofillFieldMetadata.cs +++ b/src/Android/Autofill/Field.cs @@ -8,29 +8,29 @@ using Android.Text; namespace Bit.Android.Autofill { - public class AutofillFieldMetadata + public class Field { - private List _autofillHints; + private List _hints; private string[] _autofillOptions; - public AutofillFieldMetadata(ViewNode view) + public Field(ViewNode view) { _autofillOptions = view.GetAutofillOptions(); Id = view.Id; AutofillId = view.AutofillId; AutofillType = view.AutofillType; InputType = view.InputType; - IsFocused = view.IsFocused; - AutofillHints = AutofillHelper.FilterForSupportedHints(view.GetAutofillHints())?.ToList() ?? new List(); + Focused = view.IsFocused; + Hints = AutofillHelpers.FilterForSupportedHints(view.GetAutofillHints())?.ToList() ?? new List(); } public SaveDataType SaveType { get; set; } = SaveDataType.Generic; - public List AutofillHints + public List Hints { - get { return _autofillHints; } + get => _hints; set { - _autofillHints = value; + _hints = value; UpdateSaveTypeFromHints(); } } @@ -38,7 +38,7 @@ namespace Bit.Android.Autofill public AutofillId AutofillId { get; private set; } public AutofillType AutofillType { get; private set; } public InputTypes InputType { get; private set; } - public bool IsFocused { get; private set; } + public bool Focused { get; private set; } /** * When the {@link ViewNode} is a list that the user needs to choose a string from (i.e. a @@ -60,12 +60,12 @@ namespace Bit.Android.Autofill private void UpdateSaveTypeFromHints() { SaveType = SaveDataType.Generic; - if(_autofillHints == null) + if(_hints == null) { return; } - foreach(var hint in _autofillHints) + foreach(var hint in _hints) { switch(hint) { diff --git a/src/Android/Autofill/FieldCollection.cs b/src/Android/Autofill/FieldCollection.cs new file mode 100644 index 000000000..dd611143c --- /dev/null +++ b/src/Android/Autofill/FieldCollection.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using Android.Service.Autofill; +using Android.Views.Autofill; + +namespace Bit.Android.Autofill +{ + public class FieldCollection + { + public HashSet Ids { get; private set; } = new HashSet(); + public List AutofillIds { get; private set; } = new List(); + public SaveDataType SaveType { get; private set; } = SaveDataType.Generic; + public List Hints { get; private set; } = new List(); + public List FocusedHints { get; private set; } = new List(); + public List Fields { get; private set; } = new List(); + public IDictionary IdToFieldMap { get; private set; } = + new Dictionary(); + public IDictionary> HintToFieldsMap { get; private set; } = + new Dictionary>(); + + public void Add(Field field) + { + if(Ids.Contains(field.Id)) + { + return; + } + + SaveType |= field.SaveType; + Ids.Add(field.Id); + Fields.Add(field); + AutofillIds.Add(field.AutofillId); + IdToFieldMap.Add(field.Id, field); + + if((field.Hints?.Count ?? 0) > 0) + { + Hints.AddRange(field.Hints); + if(field.Focused) + { + FocusedHints.AddRange(field.Hints); + } + + foreach(var hint in field.Hints) + { + if(!HintToFieldsMap.ContainsKey(hint)) + { + HintToFieldsMap.Add(hint, new List()); + } + + HintToFieldsMap[hint].Add(field); + } + } + } + } +} \ No newline at end of file diff --git a/src/Android/Autofill/FilledAutofillField.cs b/src/Android/Autofill/FilledField.cs similarity index 50% rename from src/Android/Autofill/FilledAutofillField.cs rename to src/Android/Autofill/FilledField.cs index fe2ab14ad..3eae8e729 100644 --- a/src/Android/Autofill/FilledAutofillField.cs +++ b/src/Android/Autofill/FilledField.cs @@ -1,51 +1,51 @@ -using static Android.App.Assist.AssistStructure; +using System.Collections.Generic; +using static Android.App.Assist.AssistStructure; namespace Bit.Android.Autofill { - public class FilledAutofillField + public class FilledField { - /** - * Does not need to be serialized into persistent storage, so it's not exposed. - */ - private string[] _autofillHints = null; + private IEnumerable _hints = null; - public FilledAutofillField() { } + public FilledField() { } - public FilledAutofillField(ViewNode viewNode) + public FilledField(ViewNode viewNode) { - _autofillHints = AutofillHelper.FilterForSupportedHints(viewNode.GetAutofillHints()); + _hints = AutofillHelpers.FilterForSupportedHints(viewNode.GetAutofillHints()); var autofillValue = viewNode.AutofillValue; - if(autofillValue != null) + if(autofillValue == null) { - if(autofillValue.IsList) + return; + } + + if(autofillValue.IsList) + { + var autofillOptions = viewNode.GetAutofillOptions(); + int index = autofillValue.ListValue; + if(autofillOptions != null && autofillOptions.Length > 0) { - var autofillOptions = viewNode.GetAutofillOptions(); - int index = autofillValue.ListValue; - if(autofillOptions != null && autofillOptions.Length > 0) - { - TextValue = autofillOptions[index]; - } - } - else if(autofillValue.IsDate) - { - DateValue = autofillValue.DateValue; - } - else if(autofillValue.IsText) - { - // Using toString of AutofillValue.getTextValue in order to save it to - // SharedPreferences. - TextValue = autofillValue.TextValue; + TextValue = autofillOptions[index]; } } + else if(autofillValue.IsDate) + { + DateValue = autofillValue.DateValue; + } + else if(autofillValue.IsText) + { + // Using toString of AutofillValue.getTextValue in order to save it to + // SharedPreferences. + TextValue = autofillValue.TextValue; + } } public string TextValue { get; set; } public long? DateValue { get; set; } public bool? ToggleValue { get; set; } - public string[] GetAutofillHints() + public IEnumerable GetHints() { - return _autofillHints; + return _hints; } public bool IsNull() @@ -56,16 +56,25 @@ namespace Bit.Android.Autofill public override bool Equals(object o) { if(this == o) + { return true; + } if(o == null || GetType() != o.GetType()) + { return false; + } - var that = (FilledAutofillField)o; + var that = o as FilledField; if(TextValue != null ? !TextValue.Equals(that.TextValue) : that.TextValue != null) + { return false; + } + if(DateValue != null ? !DateValue.Equals(that.DateValue) : that.DateValue != null) + { return false; + } return ToggleValue != null ? ToggleValue.Equals(that.ToggleValue) : that.ToggleValue == null; } diff --git a/src/Android/Autofill/FilledAutofillFieldCollection.cs b/src/Android/Autofill/FilledFieldCollection.cs similarity index 62% rename from src/Android/Autofill/FilledAutofillFieldCollection.cs rename to src/Android/Autofill/FilledFieldCollection.cs index 11c563c89..f103387f9 100644 --- a/src/Android/Autofill/FilledAutofillFieldCollection.cs +++ b/src/Android/Autofill/FilledFieldCollection.cs @@ -3,39 +3,42 @@ using System.Collections.Generic; using Android.Service.Autofill; using Android.Views; using Android.Views.Autofill; +using System.Linq; +using Android.Text; namespace Bit.Android.Autofill { - public class FilledAutofillFieldCollection + public class FilledFieldCollection : IFilledItem { - public FilledAutofillFieldCollection() - : this(null, new Dictionary()) + public FilledFieldCollection() + : this(null, new Dictionary()) + { } + + public FilledFieldCollection(string datasetName, IDictionary hintMap) { + HintToFieldMap = hintMap; + Name = datasetName; + Subtitle = "username"; } - public FilledAutofillFieldCollection(string datasetName, IDictionary hintMap) - { - HintMap = hintMap; - DatasetName = datasetName; - } - - public IDictionary HintMap { get; private set; } - public string DatasetName { get; set; } + public IDictionary HintToFieldMap { get; private set; } + public string Name { get; set; } + public string Subtitle { get; set; } /** * Adds a {@code FilledAutofillField} to the collection, indexed by all of its hints. */ - public void Add(FilledAutofillField filledAutofillField) + public void Add(FilledField filledAutofillField) { if(filledAutofillField == null) { throw new ArgumentNullException(nameof(filledAutofillField)); } - var autofillHints = filledAutofillField.GetAutofillHints(); + var autofillHints = filledAutofillField.GetHints(); foreach(var hint in autofillHints) { - HintMap.Add(hint, filledAutofillField); + HintToFieldMap.Add(hint, filledAutofillField); } } @@ -48,35 +51,33 @@ namespace Bit.Android.Autofill * to Views specified in a {@code AutofillFieldMetadataCollection}, which represents the current * page the user is on. */ - public bool ApplyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection, - Dataset.Builder datasetBuilder) + public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder) { var setValueAtLeastOnce = false; - var allHints = autofillFieldMetadataCollection.AutofillHints; + var allHints = fieldCollection.Hints; for(var hintIndex = 0; hintIndex < allHints.Count; hintIndex++) { var hint = allHints[hintIndex]; - if(!autofillFieldMetadataCollection.AutofillHintsToFieldsMap.ContainsKey(hint)) + if(!fieldCollection.HintToFieldsMap.ContainsKey(hint)) { continue; } - var fillableAutofillFields = autofillFieldMetadataCollection.AutofillHintsToFieldsMap[hint]; + var fillableAutofillFields = fieldCollection.HintToFieldsMap[hint]; for(var autofillFieldIndex = 0; autofillFieldIndex < fillableAutofillFields.Count; autofillFieldIndex++) { - if(!HintMap.ContainsKey(hint)) + if(!HintToFieldMap.ContainsKey(hint)) { continue; } - var filledAutofillField = HintMap[hint]; - var autofillFieldMetadata = fillableAutofillFields[autofillFieldIndex]; - var autofillId = autofillFieldMetadata.AutofillId; - var autofillType = autofillFieldMetadata.AutofillType; - switch(autofillType) + var filledField = HintToFieldMap[hint]; + var fieldMetadata = fillableAutofillFields[autofillFieldIndex]; + var autofillId = fieldMetadata.AutofillId; + switch(fieldMetadata.AutofillType) { case AutofillType.List: - int listValue = autofillFieldMetadata.GetAutofillOptionIndex(filledAutofillField.TextValue); + int listValue = fieldMetadata.GetAutofillOptionIndex(filledField.TextValue); if(listValue != -1) { datasetBuilder.SetValue(autofillId, AutofillValue.ForList(listValue)); @@ -84,7 +85,7 @@ namespace Bit.Android.Autofill } break; case AutofillType.Date: - var dateValue = filledAutofillField.DateValue; + var dateValue = filledField.DateValue; if(dateValue != null) { datasetBuilder.SetValue(autofillId, AutofillValue.ForDate(dateValue.Value)); @@ -92,7 +93,7 @@ namespace Bit.Android.Autofill } break; case AutofillType.Text: - var textValue = filledAutofillField.TextValue; + var textValue = filledField.TextValue; if(textValue != null) { datasetBuilder.SetValue(autofillId, AutofillValue.ForText(textValue)); @@ -100,7 +101,7 @@ namespace Bit.Android.Autofill } break; case AutofillType.Toggle: - var toggleValue = filledAutofillField.ToggleValue; + var toggleValue = filledField.ToggleValue; if(toggleValue != null) { datasetBuilder.SetValue(autofillId, AutofillValue.ForToggle(toggleValue.Value)); @@ -114,6 +115,16 @@ namespace Bit.Android.Autofill } } + if(!setValueAtLeastOnce) + { + var password = fieldCollection.Fields.FirstOrDefault(f => f.InputType == InputTypes.TextVariationPassword); + // datasetBuilder.SetValue(password.AutofillId, AutofillValue.ForText()); + if(password != null) + { + var username = fieldCollection.Fields.TakeWhile(f => f.Id != password.Id).LastOrDefault(); + } + } + return setValueAtLeastOnce; } @@ -124,15 +135,7 @@ namespace Bit.Android.Autofill */ public bool HelpsWithHints(List autofillHints) { - for(var i = 0; i < autofillHints.Count; i++) - { - if(HintMap.ContainsKey(autofillHints[i]) && !HintMap[autofillHints[i]].IsNull()) - { - return true; - } - } - - return false; + return autofillHints.Any(h => HintToFieldMap.ContainsKey(h) && !HintToFieldMap[h].IsNull()); } } } \ No newline at end of file diff --git a/src/Android/Autofill/IFilledItem.cs b/src/Android/Autofill/IFilledItem.cs new file mode 100644 index 000000000..5a81808af --- /dev/null +++ b/src/Android/Autofill/IFilledItem.cs @@ -0,0 +1,12 @@ +using Android.Service.Autofill; +using System; + +namespace Bit.Android.Autofill +{ + public interface IFilledItem + { + string Name { get; set; } + string Subtitle { get; set; } + bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder); + } +} \ No newline at end of file diff --git a/src/Android/Autofill/StructureParser.cs b/src/Android/Autofill/Parser.cs similarity index 66% rename from src/Android/Autofill/StructureParser.cs rename to src/Android/Autofill/Parser.cs index 20ea1ecf1..ec43d5e33 100644 --- a/src/Android/Autofill/StructureParser.cs +++ b/src/Android/Autofill/Parser.cs @@ -3,18 +3,26 @@ using Android.App.Assist; namespace Bit.Android.Autofill { - public class StructureParser + public class Parser { private readonly AssistStructure _structure; - private FilledAutofillFieldCollection _filledAutofillFieldCollection; + private string _uri; + private FilledFieldCollection _filledAutofillFieldCollection; - public StructureParser(AssistStructure structure) + public Parser(AssistStructure structure) { _structure = structure; } - public AutofillFieldMetadataCollection AutofillFields { get; private set; } - = new AutofillFieldMetadataCollection(); + public FieldCollection FieldCollection { get; private set; } = new FieldCollection(); + public string Uri + { + get => _uri; + set + { + _uri = $"androidapp://{value}"; + } + } public void ParseForFill() { @@ -31,7 +39,7 @@ namespace Bit.Android.Autofill */ private void Parse(bool forFill) { - _filledAutofillFieldCollection = new FilledAutofillFieldCollection(); + _filledAutofillFieldCollection = new FilledFieldCollection(); for(var i = 0; i < _structure.WindowNodeCount; i++) { @@ -51,11 +59,17 @@ namespace Bit.Android.Autofill { if(forFill) { - AutofillFields.Add(new AutofillFieldMetadata(viewNode)); + var f = new Field(viewNode); + FieldCollection.Add(f); + + if(Uri == null) + { + Uri = viewNode.IdPackage; + } } else { - _filledAutofillFieldCollection.Add(new FilledAutofillField(viewNode)); + _filledAutofillFieldCollection.Add(new FilledField(viewNode)); } } @@ -65,7 +79,7 @@ namespace Bit.Android.Autofill } } - public FilledAutofillFieldCollection GetClientFormData() + public FilledFieldCollection GetClientFormData() { return _filledAutofillFieldCollection; }