refactor autofill classes. basic login support.

This commit is contained in:
Kyle Spearrin 2017-11-14 23:13:55 -05:00
parent 4f5e238685
commit 0e020924ff
12 changed files with 368 additions and 267 deletions

View file

@ -287,10 +287,12 @@
<ItemGroup> <ItemGroup>
<Compile Include="AutofillActivity.cs" /> <Compile Include="AutofillActivity.cs" />
<Compile Include="AutofillCredentials.cs" /> <Compile Include="AutofillCredentials.cs" />
<Compile Include="Autofill\AutofillFieldMetadata.cs" /> <Compile Include="Autofill\Field.cs" />
<Compile Include="Autofill\AutofillFieldMetadataCollection.cs" /> <Compile Include="Autofill\FieldCollection.cs" />
<Compile Include="Autofill\AutofillFrameworkService.cs" /> <Compile Include="Autofill\AutofillService.cs" />
<Compile Include="Autofill\AutofillHelper.cs" /> <Compile Include="Autofill\AutofillHelpers.cs" />
<Compile Include="Autofill\CipherFilledItem.cs" />
<Compile Include="Autofill\IFilledItem.cs" />
<Compile Include="Controls\CustomLabelRenderer.cs" /> <Compile Include="Controls\CustomLabelRenderer.cs" />
<Compile Include="Controls\CustomSearchBarRenderer.cs" /> <Compile Include="Controls\CustomSearchBarRenderer.cs" />
<Compile Include="Controls\CustomButtonRenderer.cs" /> <Compile Include="Controls\CustomButtonRenderer.cs" />
@ -298,8 +300,8 @@
<Compile Include="Controls\ExtendedButtonRenderer.cs" /> <Compile Include="Controls\ExtendedButtonRenderer.cs" />
<Compile Include="Controls\ExtendedTabbedPageRenderer.cs" /> <Compile Include="Controls\ExtendedTabbedPageRenderer.cs" />
<Compile Include="Controls\ExtendedTableViewRenderer.cs" /> <Compile Include="Controls\ExtendedTableViewRenderer.cs" />
<Compile Include="Autofill\FilledAutofillField.cs" /> <Compile Include="Autofill\FilledField.cs" />
<Compile Include="Autofill\FilledAutofillFieldCollection.cs" /> <Compile Include="Autofill\FilledFieldCollection.cs" />
<Compile Include="HockeyAppCrashManagerListener.cs" /> <Compile Include="HockeyAppCrashManagerListener.cs" />
<Compile Include="AutofillService.cs" /> <Compile Include="AutofillService.cs" />
<Compile Include="Controls\ExtendedEditorRenderer.cs" /> <Compile Include="Controls\ExtendedEditorRenderer.cs" />
@ -328,7 +330,7 @@
<Compile Include="Services\SqlService.cs" /> <Compile Include="Services\SqlService.cs" />
<Compile Include="SplashActivity.cs" /> <Compile Include="SplashActivity.cs" />
<Compile Include="PackageReplacedReceiver.cs" /> <Compile Include="PackageReplacedReceiver.cs" />
<Compile Include="Autofill\StructureParser.cs" /> <Compile Include="Autofill\Parser.cs" />
<Compile Include="Utilities.cs" /> <Compile Include="Utilities.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -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<int> Ids { get; private set; } = new List<int>();
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
public SaveDataType SaveType { get; private set; } = SaveDataType.Generic;
public List<string> AutofillHints { get; private set; } = new List<string>();
public List<string> FocusedAutofillHints { get; private set; } = new List<string>();
public List<AutofillFieldMetadata> Feilds { get; private set; }
public IDictionary<int, AutofillFieldMetadata> IdToFieldMap { get; private set; } =
new Dictionary<int, AutofillFieldMetadata>();
public IDictionary<string, List<AutofillFieldMetadata>> AutofillHintsToFieldsMap { get; private set; } =
new Dictionary<string, List<AutofillFieldMetadata>>();
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<AutofillFieldMetadata>());
}
AutofillHintsToFieldsMap[hint].Add(data);
}
}
}
}
}

View file

@ -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<string, FilledAutofillField>
{
{ 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<string, FilledAutofillField>
{
{ View.AutofillHintUsername, username2 },
{ View.AutofillHintPassword, password2 }
};
var col2 = new FilledAutofillFieldCollection("Login 2 Name", login2);
var clientFormDataMap = new Dictionary<string, FilledAutofillFieldCollection>
{
{ "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);
}
}
}

View file

@ -4,37 +4,38 @@ using Android.Content;
using Android.Service.Autofill; using Android.Service.Autofill;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
using System.Diagnostics;
using System.Linq;
namespace Bit.Android.Autofill 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 * Wraps autofill data in a LoginCredential Dataset object which can then be sent back to the
* client View. * client View.
*/ */
public static Dataset NewDataset(Context context, AutofillFieldMetadataCollection autofillFields, public static Dataset NewDataset(Context context, FieldCollection fields, IFilledItem filledItem, bool auth)
FilledAutofillFieldCollection filledAutofillFieldCollection, bool datasetAuth)
{ {
var datasetName = filledAutofillFieldCollection.DatasetName; var itemName = filledItem.Name;
if(datasetName != null) if(itemName != null)
{ {
Dataset.Builder datasetBuilder; Dataset.Builder datasetBuilder;
if(datasetAuth) if(auth)
{ {
datasetBuilder = new Dataset.Builder( 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); //IntentSender sender = AuthActivity.getAuthIntentSenderForDataset(context, datasetName);
//datasetBuilder.SetAuthentication(sender); //datasetBuilder.SetAuthentication(sender);
} }
else else
{ {
datasetBuilder = new Dataset.Builder( 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); var setValue = filledItem.ApplyToFields(fields, datasetBuilder);
if(setValueAtLeastOnce) if(setValue)
{ {
return datasetBuilder.Build(); 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 * Wraps autofill data in a Response object (essentially a series of Datasets) which can then
* be sent back to the client View. * be sent back to the client View.
*/ */
public static FillResponse NewResponse(Context context, bool datasetAuth, public static FillResponse NewResponse(Context context, bool auth, FieldCollection fields,
AutofillFieldMetadataCollection autofillFields, IDictionary<string, IFilledItem> items)
IDictionary<string, FilledAutofillFieldCollection> clientFormDataMap)
{ {
var responseBuilder = new FillResponse.Builder(); 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); responseBuilder.AddDataset(dataset);
if(dataset != null)
{
responseBuilder.AddDataset(dataset);
}
} }
} }
} }
if(autofillFields.SaveType != SaveDataType.Generic) if(true || fields.SaveType != SaveDataType.Generic)
{ {
responseBuilder.SetSaveInfo( var info = new SaveInfo.Builder(fields.SaveType, fields.AutofillIds.ToArray()).Build();
new SaveInfo.Builder(autofillFields.SaveType, autofillFields.AutofillIds.ToArray()).Build()); responseBuilder.SetSaveInfo(info);
return responseBuilder.Build(); return responseBuilder.Build();
} }
else 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; return null;
} }
} }
public static string[] FilterForSupportedHints(string[] hints) public static List<string> FilterForSupportedHints(string[] hints)
{ {
if((hints?.Length ?? 0) == 0) if(hints == null)
{ {
return new string[0]; return new List<string>();
} }
var filteredHints = new string[hints.Length]; return hints.Where(h => IsValidHint(h)).ToList();
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;
} }
public static bool IsValidHint(string hint) public static bool IsValidHint(string hint)

View file

@ -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<ICipherService>();
}
// build response
var items = new Dictionary<string, IFilledItem>();
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);
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -8,29 +8,29 @@ using Android.Text;
namespace Bit.Android.Autofill namespace Bit.Android.Autofill
{ {
public class AutofillFieldMetadata public class Field
{ {
private List<string> _autofillHints; private List<string> _hints;
private string[] _autofillOptions; private string[] _autofillOptions;
public AutofillFieldMetadata(ViewNode view) public Field(ViewNode view)
{ {
_autofillOptions = view.GetAutofillOptions(); _autofillOptions = view.GetAutofillOptions();
Id = view.Id; Id = view.Id;
AutofillId = view.AutofillId; AutofillId = view.AutofillId;
AutofillType = view.AutofillType; AutofillType = view.AutofillType;
InputType = view.InputType; InputType = view.InputType;
IsFocused = view.IsFocused; Focused = view.IsFocused;
AutofillHints = AutofillHelper.FilterForSupportedHints(view.GetAutofillHints())?.ToList() ?? new List<string>(); Hints = AutofillHelpers.FilterForSupportedHints(view.GetAutofillHints())?.ToList() ?? new List<string>();
} }
public SaveDataType SaveType { get; set; } = SaveDataType.Generic; public SaveDataType SaveType { get; set; } = SaveDataType.Generic;
public List<string> AutofillHints public List<string> Hints
{ {
get { return _autofillHints; } get => _hints;
set set
{ {
_autofillHints = value; _hints = value;
UpdateSaveTypeFromHints(); UpdateSaveTypeFromHints();
} }
} }
@ -38,7 +38,7 @@ namespace Bit.Android.Autofill
public AutofillId AutofillId { get; private set; } public AutofillId AutofillId { get; private set; }
public AutofillType AutofillType { get; private set; } public AutofillType AutofillType { get; private set; }
public InputTypes InputType { 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 * 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() private void UpdateSaveTypeFromHints()
{ {
SaveType = SaveDataType.Generic; SaveType = SaveDataType.Generic;
if(_autofillHints == null) if(_hints == null)
{ {
return; return;
} }
foreach(var hint in _autofillHints) foreach(var hint in _hints)
{ {
switch(hint) switch(hint)
{ {

View file

@ -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<int> Ids { get; private set; } = new HashSet<int>();
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
public SaveDataType SaveType { get; private set; } = SaveDataType.Generic;
public List<string> Hints { get; private set; } = new List<string>();
public List<string> FocusedHints { get; private set; } = new List<string>();
public List<Field> Fields { get; private set; } = new List<Field>();
public IDictionary<int, Field> IdToFieldMap { get; private set; } =
new Dictionary<int, Field>();
public IDictionary<string, List<Field>> HintToFieldsMap { get; private set; } =
new Dictionary<string, List<Field>>();
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<Field>());
}
HintToFieldsMap[hint].Add(field);
}
}
}
}
}

View file

@ -1,51 +1,51 @@
using static Android.App.Assist.AssistStructure; using System.Collections.Generic;
using static Android.App.Assist.AssistStructure;
namespace Bit.Android.Autofill namespace Bit.Android.Autofill
{ {
public class FilledAutofillField public class FilledField
{ {
/** private IEnumerable<string> _hints = null;
* Does not need to be serialized into persistent storage, so it's not exposed.
*/
private string[] _autofillHints = 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; 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(); TextValue = autofillOptions[index];
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;
} }
} }
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 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[] GetAutofillHints() public IEnumerable<string> GetHints()
{ {
return _autofillHints; return _hints;
} }
public bool IsNull() public bool IsNull()
@ -56,16 +56,25 @@ namespace Bit.Android.Autofill
public override bool Equals(object o) public override bool Equals(object o)
{ {
if(this == o) if(this == o)
{
return true; return true;
}
if(o == null || GetType() != o.GetType()) if(o == null || GetType() != o.GetType())
{
return false; return false;
}
var that = (FilledAutofillField)o; var that = o as FilledField;
if(TextValue != null ? !TextValue.Equals(that.TextValue) : that.TextValue != null) if(TextValue != null ? !TextValue.Equals(that.TextValue) : that.TextValue != null)
{
return false; return false;
}
if(DateValue != null ? !DateValue.Equals(that.DateValue) : that.DateValue != null) if(DateValue != null ? !DateValue.Equals(that.DateValue) : that.DateValue != null)
{
return false; return false;
}
return ToggleValue != null ? ToggleValue.Equals(that.ToggleValue) : that.ToggleValue == null; return ToggleValue != null ? ToggleValue.Equals(that.ToggleValue) : that.ToggleValue == null;
} }

View file

@ -3,39 +3,42 @@ using System.Collections.Generic;
using Android.Service.Autofill; using Android.Service.Autofill;
using Android.Views; using Android.Views;
using Android.Views.Autofill; using Android.Views.Autofill;
using System.Linq;
using Android.Text;
namespace Bit.Android.Autofill namespace Bit.Android.Autofill
{ {
public class FilledAutofillFieldCollection public class FilledFieldCollection : IFilledItem
{ {
public FilledAutofillFieldCollection() public FilledFieldCollection()
: this(null, new Dictionary<string, FilledAutofillField>()) : this(null, new Dictionary<string, FilledField>())
{ }
public FilledFieldCollection(string datasetName, IDictionary<string, FilledField> hintMap)
{ {
HintToFieldMap = hintMap;
Name = datasetName;
Subtitle = "username";
} }
public FilledAutofillFieldCollection(string datasetName, IDictionary<string, FilledAutofillField> hintMap) public IDictionary<string, FilledField> HintToFieldMap { get; private set; }
{ public string Name { get; set; }
HintMap = hintMap; public string Subtitle { get; set; }
DatasetName = datasetName;
}
public IDictionary<string, FilledAutofillField> HintMap { get; private set; }
public string DatasetName { get; set; }
/** /**
* Adds a {@code FilledAutofillField} to the collection, indexed by all of its hints. * 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) if(filledAutofillField == null)
{ {
throw new ArgumentNullException(nameof(filledAutofillField)); throw new ArgumentNullException(nameof(filledAutofillField));
} }
var autofillHints = filledAutofillField.GetAutofillHints(); var autofillHints = filledAutofillField.GetHints();
foreach(var hint in autofillHints) 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 * to Views specified in a {@code AutofillFieldMetadataCollection}, which represents the current
* page the user is on. * page the user is on.
*/ */
public bool ApplyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection, public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder)
Dataset.Builder datasetBuilder)
{ {
var setValueAtLeastOnce = false; var setValueAtLeastOnce = false;
var allHints = autofillFieldMetadataCollection.AutofillHints; var allHints = fieldCollection.Hints;
for(var hintIndex = 0; hintIndex < allHints.Count; hintIndex++) for(var hintIndex = 0; hintIndex < allHints.Count; hintIndex++)
{ {
var hint = allHints[hintIndex]; var hint = allHints[hintIndex];
if(!autofillFieldMetadataCollection.AutofillHintsToFieldsMap.ContainsKey(hint)) if(!fieldCollection.HintToFieldsMap.ContainsKey(hint))
{ {
continue; continue;
} }
var fillableAutofillFields = autofillFieldMetadataCollection.AutofillHintsToFieldsMap[hint]; var fillableAutofillFields = fieldCollection.HintToFieldsMap[hint];
for(var autofillFieldIndex = 0; autofillFieldIndex < fillableAutofillFields.Count; autofillFieldIndex++) for(var autofillFieldIndex = 0; autofillFieldIndex < fillableAutofillFields.Count; autofillFieldIndex++)
{ {
if(!HintMap.ContainsKey(hint)) if(!HintToFieldMap.ContainsKey(hint))
{ {
continue; continue;
} }
var filledAutofillField = HintMap[hint]; var filledField = HintToFieldMap[hint];
var autofillFieldMetadata = fillableAutofillFields[autofillFieldIndex]; var fieldMetadata = fillableAutofillFields[autofillFieldIndex];
var autofillId = autofillFieldMetadata.AutofillId; var autofillId = fieldMetadata.AutofillId;
var autofillType = autofillFieldMetadata.AutofillType; switch(fieldMetadata.AutofillType)
switch(autofillType)
{ {
case AutofillType.List: case AutofillType.List:
int listValue = autofillFieldMetadata.GetAutofillOptionIndex(filledAutofillField.TextValue); int listValue = fieldMetadata.GetAutofillOptionIndex(filledField.TextValue);
if(listValue != -1) if(listValue != -1)
{ {
datasetBuilder.SetValue(autofillId, AutofillValue.ForList(listValue)); datasetBuilder.SetValue(autofillId, AutofillValue.ForList(listValue));
@ -84,7 +85,7 @@ namespace Bit.Android.Autofill
} }
break; break;
case AutofillType.Date: case AutofillType.Date:
var dateValue = filledAutofillField.DateValue; var dateValue = filledField.DateValue;
if(dateValue != null) if(dateValue != null)
{ {
datasetBuilder.SetValue(autofillId, AutofillValue.ForDate(dateValue.Value)); datasetBuilder.SetValue(autofillId, AutofillValue.ForDate(dateValue.Value));
@ -92,7 +93,7 @@ namespace Bit.Android.Autofill
} }
break; break;
case AutofillType.Text: case AutofillType.Text:
var textValue = filledAutofillField.TextValue; var textValue = filledField.TextValue;
if(textValue != null) if(textValue != null)
{ {
datasetBuilder.SetValue(autofillId, AutofillValue.ForText(textValue)); datasetBuilder.SetValue(autofillId, AutofillValue.ForText(textValue));
@ -100,7 +101,7 @@ namespace Bit.Android.Autofill
} }
break; break;
case AutofillType.Toggle: case AutofillType.Toggle:
var toggleValue = filledAutofillField.ToggleValue; var toggleValue = filledField.ToggleValue;
if(toggleValue != null) if(toggleValue != null)
{ {
datasetBuilder.SetValue(autofillId, AutofillValue.ForToggle(toggleValue.Value)); 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; return setValueAtLeastOnce;
} }
@ -124,15 +135,7 @@ namespace Bit.Android.Autofill
*/ */
public bool HelpsWithHints(List<String> autofillHints) public bool HelpsWithHints(List<String> autofillHints)
{ {
for(var i = 0; i < autofillHints.Count; i++) return autofillHints.Any(h => HintToFieldMap.ContainsKey(h) && !HintToFieldMap[h].IsNull());
{
if(HintMap.ContainsKey(autofillHints[i]) && !HintMap[autofillHints[i]].IsNull())
{
return true;
}
}
return false;
} }
} }
} }

View file

@ -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);
}
}

View file

@ -3,18 +3,26 @@ using Android.App.Assist;
namespace Bit.Android.Autofill namespace Bit.Android.Autofill
{ {
public class StructureParser public class Parser
{ {
private readonly AssistStructure _structure; private readonly AssistStructure _structure;
private FilledAutofillFieldCollection _filledAutofillFieldCollection; private string _uri;
private FilledFieldCollection _filledAutofillFieldCollection;
public StructureParser(AssistStructure structure) public Parser(AssistStructure structure)
{ {
_structure = structure; _structure = structure;
} }
public AutofillFieldMetadataCollection AutofillFields { get; private set; } public FieldCollection FieldCollection { get; private set; } = new FieldCollection();
= new AutofillFieldMetadataCollection(); public string Uri
{
get => _uri;
set
{
_uri = $"androidapp://{value}";
}
}
public void ParseForFill() public void ParseForFill()
{ {
@ -31,7 +39,7 @@ namespace Bit.Android.Autofill
*/ */
private void Parse(bool forFill) private void Parse(bool forFill)
{ {
_filledAutofillFieldCollection = new FilledAutofillFieldCollection(); _filledAutofillFieldCollection = new FilledFieldCollection();
for(var i = 0; i < _structure.WindowNodeCount; i++) for(var i = 0; i < _structure.WindowNodeCount; i++)
{ {
@ -51,11 +59,17 @@ namespace Bit.Android.Autofill
{ {
if(forFill) if(forFill)
{ {
AutofillFields.Add(new AutofillFieldMetadata(viewNode)); var f = new Field(viewNode);
FieldCollection.Add(f);
if(Uri == null)
{
Uri = viewNode.IdPackage;
}
} }
else 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; return _filledAutofillFieldCollection;
} }