diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index e0358a9a6..cab965c6c 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -287,8 +287,10 @@
-
-
+
+
+
+
@@ -296,6 +298,8 @@
+
+
@@ -324,6 +328,7 @@
+
diff --git a/src/Android/Autofill/AutofillFieldMetadata.cs b/src/Android/Autofill/AutofillFieldMetadata.cs
new file mode 100644
index 000000000..1eb9294a4
--- /dev/null
+++ b/src/Android/Autofill/AutofillFieldMetadata.cs
@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using System.Linq;
+using Android.Service.Autofill;
+using Android.Views;
+using Android.Views.Autofill;
+using static Android.App.Assist.AssistStructure;
+using Android.Text;
+
+namespace Bit.Android.Autofill
+{
+ public class AutofillFieldMetadata
+ {
+ private List _autofillHints;
+ private string[] _autofillOptions;
+
+ public AutofillFieldMetadata(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();
+ }
+
+ public SaveDataType SaveType { get; set; } = SaveDataType.Generic;
+ public List AutofillHints
+ {
+ get { return _autofillHints; }
+ set
+ {
+ _autofillHints = value;
+ UpdateSaveTypeFromHints();
+ }
+ }
+ public int Id { get; private set; }
+ public AutofillId AutofillId { get; private set; }
+ public AutofillType AutofillType { get; private set; }
+ public InputTypes InputType { get; private set; }
+ public bool IsFocused { get; private set; }
+
+ /**
+ * When the {@link ViewNode} is a list that the user needs to choose a string from (i.e. a
+ * spinner), this is called to return the index of a specific item in the list.
+ */
+ public int GetAutofillOptionIndex(string value)
+ {
+ for(var i = 0; i < _autofillOptions.Length; i++)
+ {
+ if(_autofillOptions[i].Equals(value))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private void UpdateSaveTypeFromHints()
+ {
+ SaveType = SaveDataType.Generic;
+ if(_autofillHints == null)
+ {
+ return;
+ }
+
+ foreach(var hint in _autofillHints)
+ {
+ switch(hint)
+ {
+ case View.AutofillHintCreditCardExpirationDate:
+ case View.AutofillHintCreditCardExpirationDay:
+ case View.AutofillHintCreditCardExpirationMonth:
+ case View.AutofillHintCreditCardExpirationYear:
+ case View.AutofillHintCreditCardNumber:
+ case View.AutofillHintCreditCardSecurityCode:
+ SaveType |= SaveDataType.CreditCard;
+ break;
+ case View.AutofillHintEmailAddress:
+ SaveType |= SaveDataType.EmailAddress;
+ break;
+ case View.AutofillHintPhone:
+ case View.AutofillHintName:
+ SaveType |= SaveDataType.Generic;
+ break;
+ case View.AutofillHintPassword:
+ SaveType |= SaveDataType.Password;
+ SaveType &= ~SaveDataType.EmailAddress;
+ SaveType &= ~SaveDataType.Username;
+ break;
+ case View.AutofillHintPostalAddress:
+ case View.AutofillHintPostalCode:
+ SaveType |= SaveDataType.Address;
+ break;
+ case View.AutofillHintUsername:
+ SaveType |= SaveDataType.Username;
+ break;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Android/Autofill/AutofillFieldMetadataCollection.cs b/src/Android/Autofill/AutofillFieldMetadataCollection.cs
new file mode 100644
index 000000000..449a3f0d3
--- /dev/null
+++ b/src/Android/Autofill/AutofillFieldMetadataCollection.cs
@@ -0,0 +1,51 @@
+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
new file mode 100644
index 000000000..123b85fe4
--- /dev/null
+++ b/src/Android/Autofill/AutofillFrameworkService.cs
@@ -0,0 +1,80 @@
+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/AutofillHelper.cs
new file mode 100644
index 000000000..bfc5e7313
--- /dev/null
+++ b/src/Android/Autofill/AutofillHelper.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using Android.Content;
+using Android.Service.Autofill;
+using Android.Views;
+using Android.Widget;
+
+namespace Bit.Android.Autofill
+{
+ public static class AutofillHelper
+ {
+ /**
+ * 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)
+ {
+ var datasetName = filledAutofillFieldCollection.DatasetName;
+ if(datasetName != null)
+ {
+ Dataset.Builder datasetBuilder;
+ if(datasetAuth)
+ {
+ datasetBuilder = new Dataset.Builder(
+ NewRemoteViews(context.PackageName, datasetName, "username", 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));
+ }
+
+ var setValueAtLeastOnce = filledAutofillFieldCollection.ApplyToFields(autofillFields, datasetBuilder);
+ if(setValueAtLeastOnce)
+ {
+ return datasetBuilder.Build();
+ }
+ }
+
+ return null;
+ }
+
+ public static RemoteViews NewRemoteViews(string packageName, string text, string subtext, int iconId)
+ {
+ var views = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
+ views.SetTextViewText(Resource.Id.text, text);
+ views.SetTextViewText(Resource.Id.text2, subtext);
+ views.SetImageViewResource(Resource.Id.icon, iconId);
+ return views;
+ }
+
+ /**
+ * 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)
+ {
+ var responseBuilder = new FillResponse.Builder();
+ if(clientFormDataMap != null)
+ {
+ foreach(var datasetName in clientFormDataMap.Keys)
+ {
+ if(clientFormDataMap.ContainsKey(datasetName))
+ {
+ var dataset = NewDataset(context, autofillFields, clientFormDataMap[datasetName], datasetAuth);
+ if(dataset != null)
+ {
+ responseBuilder.AddDataset(dataset);
+ }
+ }
+ }
+ }
+
+ if(autofillFields.SaveType != SaveDataType.Generic)
+ {
+ responseBuilder.SetSaveInfo(
+ new SaveInfo.Builder(autofillFields.SaveType, autofillFields.AutofillIds.ToArray()).Build());
+ return responseBuilder.Build();
+ }
+ else
+ {
+ //Log.d(TAG, "These fields are not meant to be saved by autofill.");
+ return null;
+ }
+ }
+
+ public static string[] FilterForSupportedHints(string[] hints)
+ {
+ if((hints?.Length ?? 0) == 0)
+ {
+ return new string[0];
+ }
+
+ 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;
+ }
+
+ public static bool IsValidHint(string hint)
+ {
+ switch(hint)
+ {
+ case View.AutofillHintCreditCardExpirationDate:
+ case View.AutofillHintCreditCardExpirationDay:
+ case View.AutofillHintCreditCardExpirationMonth:
+ case View.AutofillHintCreditCardExpirationYear:
+ case View.AutofillHintCreditCardNumber:
+ case View.AutofillHintCreditCardSecurityCode:
+ case View.AutofillHintEmailAddress:
+ case View.AutofillHintPhone:
+ case View.AutofillHintName:
+ case View.AutofillHintPassword:
+ case View.AutofillHintPostalAddress:
+ case View.AutofillHintPostalCode:
+ case View.AutofillHintUsername:
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Android/Autofill/FilledAutofillField.cs b/src/Android/Autofill/FilledAutofillField.cs
new file mode 100644
index 000000000..fe2ab14ad
--- /dev/null
+++ b/src/Android/Autofill/FilledAutofillField.cs
@@ -0,0 +1,81 @@
+using static Android.App.Assist.AssistStructure;
+
+namespace Bit.Android.Autofill
+{
+ public class FilledAutofillField
+ {
+ /**
+ * Does not need to be serialized into persistent storage, so it's not exposed.
+ */
+ private string[] _autofillHints = null;
+
+ public FilledAutofillField() { }
+
+ public FilledAutofillField(ViewNode viewNode)
+ {
+ _autofillHints = AutofillHelper.FilterForSupportedHints(viewNode.GetAutofillHints());
+ var autofillValue = viewNode.AutofillValue;
+ if(autofillValue != null)
+ {
+ if(autofillValue.IsList)
+ {
+ 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;
+ }
+ }
+ }
+
+ public string TextValue { get; set; }
+ public long? DateValue { get; set; }
+ public bool? ToggleValue { get; set; }
+
+ public string[] GetAutofillHints()
+ {
+ return _autofillHints;
+ }
+
+ public bool IsNull()
+ {
+ return TextValue == null && DateValue == null && ToggleValue == null;
+ }
+
+ public override bool Equals(object o)
+ {
+ if(this == o)
+ return true;
+
+ if(o == null || GetType() != o.GetType())
+ return false;
+
+ var that = (FilledAutofillField)o;
+ 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;
+ }
+
+ public override int GetHashCode()
+ {
+ var result = TextValue != null ? TextValue.GetHashCode() : 0;
+ result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0);
+ result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0);
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Android/Autofill/FilledAutofillFieldCollection.cs b/src/Android/Autofill/FilledAutofillFieldCollection.cs
new file mode 100644
index 000000000..11c563c89
--- /dev/null
+++ b/src/Android/Autofill/FilledAutofillFieldCollection.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using Android.Service.Autofill;
+using Android.Views;
+using Android.Views.Autofill;
+
+namespace Bit.Android.Autofill
+{
+ public class FilledAutofillFieldCollection
+ {
+ public FilledAutofillFieldCollection()
+ : this(null, new Dictionary())
+ {
+ }
+
+ public FilledAutofillFieldCollection(string datasetName, IDictionary hintMap)
+ {
+ HintMap = hintMap;
+ DatasetName = datasetName;
+ }
+
+ public IDictionary HintMap { get; private set; }
+ public string DatasetName { get; set; }
+
+ /**
+ * Adds a {@code FilledAutofillField} to the collection, indexed by all of its hints.
+ */
+ public void Add(FilledAutofillField filledAutofillField)
+ {
+ if(filledAutofillField == null)
+ {
+ throw new ArgumentNullException(nameof(filledAutofillField));
+ }
+
+ var autofillHints = filledAutofillField.GetAutofillHints();
+ foreach(var hint in autofillHints)
+ {
+ HintMap.Add(hint, filledAutofillField);
+ }
+ }
+
+ /**
+ * Populates a {@link Dataset.Builder} with appropriate values for each {@link AutofillId}
+ * in a {@code AutofillFieldMetadataCollection}.
+ *
+ * In other words, it constructs an autofill
+ * {@link Dataset.Builder} by applying saved values (from this {@code FilledAutofillFieldCollection})
+ * to Views specified in a {@code AutofillFieldMetadataCollection}, which represents the current
+ * page the user is on.
+ */
+ public bool ApplyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection,
+ Dataset.Builder datasetBuilder)
+ {
+ var setValueAtLeastOnce = false;
+ var allHints = autofillFieldMetadataCollection.AutofillHints;
+ for(var hintIndex = 0; hintIndex < allHints.Count; hintIndex++)
+ {
+ var hint = allHints[hintIndex];
+ if(!autofillFieldMetadataCollection.AutofillHintsToFieldsMap.ContainsKey(hint))
+ {
+ continue;
+ }
+
+ var fillableAutofillFields = autofillFieldMetadataCollection.AutofillHintsToFieldsMap[hint];
+ for(var autofillFieldIndex = 0; autofillFieldIndex < fillableAutofillFields.Count; autofillFieldIndex++)
+ {
+ if(!HintMap.ContainsKey(hint))
+ {
+ continue;
+ }
+
+ var filledAutofillField = HintMap[hint];
+ var autofillFieldMetadata = fillableAutofillFields[autofillFieldIndex];
+ var autofillId = autofillFieldMetadata.AutofillId;
+ var autofillType = autofillFieldMetadata.AutofillType;
+ switch(autofillType)
+ {
+ case AutofillType.List:
+ int listValue = autofillFieldMetadata.GetAutofillOptionIndex(filledAutofillField.TextValue);
+ if(listValue != -1)
+ {
+ datasetBuilder.SetValue(autofillId, AutofillValue.ForList(listValue));
+ setValueAtLeastOnce = true;
+ }
+ break;
+ case AutofillType.Date:
+ var dateValue = filledAutofillField.DateValue;
+ if(dateValue != null)
+ {
+ datasetBuilder.SetValue(autofillId, AutofillValue.ForDate(dateValue.Value));
+ setValueAtLeastOnce = true;
+ }
+ break;
+ case AutofillType.Text:
+ var textValue = filledAutofillField.TextValue;
+ if(textValue != null)
+ {
+ datasetBuilder.SetValue(autofillId, AutofillValue.ForText(textValue));
+ setValueAtLeastOnce = true;
+ }
+ break;
+ case AutofillType.Toggle:
+ var toggleValue = filledAutofillField.ToggleValue;
+ if(toggleValue != null)
+ {
+ datasetBuilder.SetValue(autofillId, AutofillValue.ForToggle(toggleValue.Value));
+ setValueAtLeastOnce = true;
+ }
+ break;
+ case AutofillType.None:
+ default:
+ break;
+ }
+ }
+ }
+
+ return setValueAtLeastOnce;
+ }
+
+ /**
+ * Takes in a list of autofill hints (`autofillHints`), usually associated with a View or set of
+ * Views. Returns whether any of the filled fields on the page have at least 1 of these
+ * `autofillHint`s.
+ */
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Android/Autofill/StructureParser.cs b/src/Android/Autofill/StructureParser.cs
new file mode 100644
index 000000000..20ea1ecf1
--- /dev/null
+++ b/src/Android/Autofill/StructureParser.cs
@@ -0,0 +1,73 @@
+using static Android.App.Assist.AssistStructure;
+using Android.App.Assist;
+
+namespace Bit.Android.Autofill
+{
+ public class StructureParser
+ {
+ private readonly AssistStructure _structure;
+ private FilledAutofillFieldCollection _filledAutofillFieldCollection;
+
+ public StructureParser(AssistStructure structure)
+ {
+ _structure = structure;
+ }
+
+ public AutofillFieldMetadataCollection AutofillFields { get; private set; }
+ = new AutofillFieldMetadataCollection();
+
+ public void ParseForFill()
+ {
+ Parse(true);
+ }
+
+ public void ParseForSave()
+ {
+ Parse(false);
+ }
+
+ /**
+ * Traverse AssistStructure and add ViewNode metadata to a flat list.
+ */
+ private void Parse(bool forFill)
+ {
+ _filledAutofillFieldCollection = new FilledAutofillFieldCollection();
+
+ for(var i = 0; i < _structure.WindowNodeCount; i++)
+ {
+ var node = _structure.GetWindowNodeAt(i);
+ var view = node.RootViewNode;
+ ParseLocked(forFill, view);
+ }
+ }
+
+ private void ParseLocked(bool forFill, ViewNode viewNode)
+ {
+ var autofillHints = viewNode.GetAutofillHints();
+ var autofillType = viewNode.AutofillType;
+ var inputType = viewNode.InputType;
+ var isEditText = viewNode.ClassName == "android.widget.EditText";
+ if(isEditText || (autofillHints?.Length ?? 0) > 0)
+ {
+ if(forFill)
+ {
+ AutofillFields.Add(new AutofillFieldMetadata(viewNode));
+ }
+ else
+ {
+ _filledAutofillFieldCollection.Add(new FilledAutofillField(viewNode));
+ }
+ }
+
+ for(var i = 0; i < viewNode.ChildCount; i++)
+ {
+ ParseLocked(forFill, viewNode.GetChildAt(i));
+ }
+ }
+
+ public FilledAutofillFieldCollection GetClientFormData()
+ {
+ return _filledAutofillFieldCollection;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Android/AutofillFrameworkService.cs b/src/Android/AutofillFrameworkService.cs
deleted file mode 100644
index 07ae9a617..000000000
--- a/src/Android/AutofillFrameworkService.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Android;
-using Android.App;
-using Android.OS;
-using Android.Runtime;
-using Android.Service.Autofill;
-using Android.Util;
-
-namespace Bit.Android
-{
- [Service(Permission = Manifest.Permission.BindAutofillService, Label = "bitwarden")]
- [IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
- [MetaData("android.autofill", Resource = "@xml/autofillservice")]
- [Register("com.x8bit.bitwarden.AutofillFrameworkService")]
- public class AutofillFrameworkService : global::Android.Service.Autofill.AutofillService
- {
- public override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)
- {
- var structure = request.FillContexts[request.FillContexts.Count - 1].Structure;
- var data = request.ClientState;
- }
-
- public override void OnSaveRequest(SaveRequest request, SaveCallback callback)
- {
- var context = request.FillContexts;
- var structure = context[context.Count - 1].Structure;
- var data = request.ClientState;
-
- }
- }
-}
diff --git a/src/Android/AutofillFrameworkService_OLD.cs b/src/Android/AutofillFrameworkService_OLD.cs
deleted file mode 100644
index cbdb69141..000000000
--- a/src/Android/AutofillFrameworkService_OLD.cs
+++ /dev/null
@@ -1,630 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-using Android.App;
-using Android.Content;
-using Android.OS;
-using Android.Runtime;
-using Android.Service.Autofill;
-using Android.Views;
-using Android.Widget;
-using Android.Views.Autofill;
-using static Android.App.Assist.AssistStructure;
-using Android.Text;
-using Android.App.Assist;
-
-namespace Bit.Android
-{
- //[Service(Permission = global::Android.Manifest.Permission.BindAutofillService, Label = "bitwarden")]
- //[IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
- //[MetaData("android.autofill", Resource = "@xml/autofillservice")]
- public class AutofillFrameworkService_OLD : 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);
- }
- }
-
- /////////////////// Helper Classes ///////////////////////
-
- public class StructureParser
- {
- private readonly AssistStructure _structure;
- private FilledAutofillFieldCollection _filledAutofillFieldCollection;
-
- public StructureParser(AssistStructure structure)
- {
- _structure = structure;
- }
-
- public AutofillFieldMetadataCollection AutofillFields { get; private set; }
- = new AutofillFieldMetadataCollection();
-
- public void ParseForFill()
- {
- Parse(true);
- }
-
- public void ParseForSave()
- {
- Parse(false);
- }
-
- /**
- * Traverse AssistStructure and add ViewNode metadata to a flat list.
- */
- private void Parse(bool forFill)
- {
- _filledAutofillFieldCollection = new FilledAutofillFieldCollection();
-
- for(var i = 0; i < _structure.WindowNodeCount; i++)
- {
- var node = _structure.GetWindowNodeAt(i);
- var view = node.RootViewNode;
- ParseLocked(forFill, view);
- }
- }
-
- private void ParseLocked(bool forFill, ViewNode viewNode)
- {
- var autofillHints = viewNode.GetAutofillHints();
- var autofillType = viewNode.AutofillType;
- var inputType = viewNode.InputType;
- var isEditText = viewNode.ClassName == "android.widget.EditText";
- if(isEditText || (autofillHints?.Length ?? 0) > 0)
- {
- if(forFill)
- {
- AutofillFields.Add(new AutofillFieldMetadata(viewNode));
- }
- else
- {
- _filledAutofillFieldCollection.Add(new FilledAutofillField(viewNode));
- }
- }
-
- for(var i = 0; i < viewNode.ChildCount; i++)
- {
- ParseLocked(forFill, viewNode.GetChildAt(i));
- }
- }
-
- public FilledAutofillFieldCollection GetClientFormData()
- {
- return _filledAutofillFieldCollection;
- }
- }
-
- 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);
- }
- }
- }
- }
-
- public class AutofillFieldMetadata
- {
- private List _autofillHints;
- private string[] _autofillOptions;
-
- public AutofillFieldMetadata(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();
- }
-
- public SaveDataType SaveType { get; set; } = SaveDataType.Generic;
- public List AutofillHints
- {
- get { return _autofillHints; }
- set
- {
- _autofillHints = value;
- UpdateSaveTypeFromHints();
- }
- }
- public int Id { get; private set; }
- public AutofillId AutofillId { get; private set; }
- public AutofillType AutofillType { get; private set; }
- public InputTypes InputType { get; private set; }
- public bool IsFocused { get; private set; }
-
- /**
- * When the {@link ViewNode} is a list that the user needs to choose a string from (i.e. a
- * spinner), this is called to return the index of a specific item in the list.
- */
- public int GetAutofillOptionIndex(string value)
- {
- for(var i = 0; i < _autofillOptions.Length; i++)
- {
- if(_autofillOptions[i].Equals(value))
- {
- return i;
- }
- }
-
- return -1;
- }
-
- private void UpdateSaveTypeFromHints()
- {
- SaveType = SaveDataType.Generic;
- if(_autofillHints == null)
- {
- return;
- }
-
- foreach(var hint in _autofillHints)
- {
- switch(hint)
- {
- case View.AutofillHintCreditCardExpirationDate:
- case View.AutofillHintCreditCardExpirationDay:
- case View.AutofillHintCreditCardExpirationMonth:
- case View.AutofillHintCreditCardExpirationYear:
- case View.AutofillHintCreditCardNumber:
- case View.AutofillHintCreditCardSecurityCode:
- SaveType |= SaveDataType.CreditCard;
- break;
- case View.AutofillHintEmailAddress:
- SaveType |= SaveDataType.EmailAddress;
- break;
- case View.AutofillHintPhone:
- case View.AutofillHintName:
- SaveType |= SaveDataType.Generic;
- break;
- case View.AutofillHintPassword:
- SaveType |= SaveDataType.Password;
- SaveType &= ~SaveDataType.EmailAddress;
- SaveType &= ~SaveDataType.Username;
- break;
- case View.AutofillHintPostalAddress:
- case View.AutofillHintPostalCode:
- SaveType |= SaveDataType.Address;
- break;
- case View.AutofillHintUsername:
- SaveType |= SaveDataType.Username;
- break;
- }
- }
- }
- }
-
- public class FilledAutofillField
- {
- /**
- * Does not need to be serialized into persistent storage, so it's not exposed.
- */
- private string[] _autofillHints = null;
-
- public FilledAutofillField() { }
-
- public FilledAutofillField(ViewNode viewNode)
- {
- _autofillHints = AutofillHelper.FilterForSupportedHints(viewNode.GetAutofillHints());
- var autofillValue = viewNode.AutofillValue;
- if(autofillValue != null)
- {
- if(autofillValue.IsList)
- {
- 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;
- }
- }
- }
-
- public string TextValue { get; set; }
- public long? DateValue { get; set; }
- public bool? ToggleValue { get; set; }
-
- public string[] GetAutofillHints()
- {
- return _autofillHints;
- }
-
- public bool IsNull()
- {
- return TextValue == null && DateValue == null && ToggleValue == null;
- }
-
- public override bool Equals(object o)
- {
- if(this == o)
- return true;
-
- if(o == null || GetType() != o.GetType())
- return false;
-
- var that = (FilledAutofillField)o;
- 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;
- }
-
- public override int GetHashCode()
- {
- var result = TextValue != null ? TextValue.GetHashCode() : 0;
- result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0);
- result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0);
- return result;
- }
- }
-
- public class FilledAutofillFieldCollection
- {
- public FilledAutofillFieldCollection()
- : this(null, new Dictionary())
- {
- }
-
- public FilledAutofillFieldCollection(string datasetName, IDictionary hintMap)
- {
- HintMap = hintMap;
- DatasetName = datasetName;
- }
-
- public IDictionary HintMap { get; private set; }
- public string DatasetName { get; set; }
-
- /**
- * Adds a {@code FilledAutofillField} to the collection, indexed by all of its hints.
- */
- public void Add(FilledAutofillField filledAutofillField)
- {
- if(filledAutofillField == null)
- {
- throw new ArgumentNullException(nameof(filledAutofillField));
- }
-
- var autofillHints = filledAutofillField.GetAutofillHints();
- foreach(var hint in autofillHints)
- {
- HintMap.Add(hint, filledAutofillField);
- }
- }
-
- /**
- * Populates a {@link Dataset.Builder} with appropriate values for each {@link AutofillId}
- * in a {@code AutofillFieldMetadataCollection}.
- *
- * In other words, it constructs an autofill
- * {@link Dataset.Builder} by applying saved values (from this {@code FilledAutofillFieldCollection})
- * to Views specified in a {@code AutofillFieldMetadataCollection}, which represents the current
- * page the user is on.
- */
- public bool ApplyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection,
- Dataset.Builder datasetBuilder)
- {
- var setValueAtLeastOnce = false;
- var allHints = autofillFieldMetadataCollection.AutofillHints;
- for(var hintIndex = 0; hintIndex < allHints.Count; hintIndex++)
- {
- var hint = allHints[hintIndex];
- if(!autofillFieldMetadataCollection.AutofillHintsToFieldsMap.ContainsKey(hint))
- {
- continue;
- }
-
- var fillableAutofillFields = autofillFieldMetadataCollection.AutofillHintsToFieldsMap[hint];
- for(var autofillFieldIndex = 0; autofillFieldIndex < fillableAutofillFields.Count; autofillFieldIndex++)
- {
- if(!HintMap.ContainsKey(hint))
- {
- continue;
- }
-
- var filledAutofillField = HintMap[hint];
- var autofillFieldMetadata = fillableAutofillFields[autofillFieldIndex];
- var autofillId = autofillFieldMetadata.AutofillId;
- var autofillType = autofillFieldMetadata.AutofillType;
- switch(autofillType)
- {
- case AutofillType.List:
- int listValue = autofillFieldMetadata.GetAutofillOptionIndex(filledAutofillField.TextValue);
- if(listValue != -1)
- {
- datasetBuilder.SetValue(autofillId, AutofillValue.ForList(listValue));
- setValueAtLeastOnce = true;
- }
- break;
- case AutofillType.Date:
- var dateValue = filledAutofillField.DateValue;
- if(dateValue != null)
- {
- datasetBuilder.SetValue(autofillId, AutofillValue.ForDate(dateValue.Value));
- setValueAtLeastOnce = true;
- }
- break;
- case AutofillType.Text:
- var textValue = filledAutofillField.TextValue;
- if(textValue != null)
- {
- datasetBuilder.SetValue(autofillId, AutofillValue.ForText(textValue));
- setValueAtLeastOnce = true;
- }
- break;
- case AutofillType.Toggle:
- var toggleValue = filledAutofillField.ToggleValue;
- if(toggleValue != null)
- {
- datasetBuilder.SetValue(autofillId, AutofillValue.ForToggle(toggleValue.Value));
- setValueAtLeastOnce = true;
- }
- break;
- case AutofillType.None:
- default:
- break;
- }
- }
- }
-
- return setValueAtLeastOnce;
- }
-
- /**
- * Takes in a list of autofill hints (`autofillHints`), usually associated with a View or set of
- * Views. Returns whether any of the filled fields on the page have at least 1 of these
- * `autofillHint`s.
- */
- 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;
- }
- }
-
- public static class AutofillHelper
- {
- /**
- * 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)
- {
- var datasetName = filledAutofillFieldCollection.DatasetName;
- if(datasetName != null)
- {
- Dataset.Builder datasetBuilder;
- if(datasetAuth)
- {
- datasetBuilder = new Dataset.Builder(
- NewRemoteViews(context.PackageName, datasetName, "username", 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));
- }
-
- var setValueAtLeastOnce = filledAutofillFieldCollection.ApplyToFields(autofillFields, datasetBuilder);
- if(setValueAtLeastOnce)
- {
- return datasetBuilder.Build();
- }
- }
-
- return null;
- }
-
- public static RemoteViews NewRemoteViews(string packageName, string text, string subtext, int iconId)
- {
- var views = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
- views.SetTextViewText(Resource.Id.text, text);
- views.SetTextViewText(Resource.Id.text2, subtext);
- views.SetImageViewResource(Resource.Id.icon, iconId);
- return views;
- }
-
- /**
- * 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)
- {
- var responseBuilder = new FillResponse.Builder();
- if(clientFormDataMap != null)
- {
- foreach(var datasetName in clientFormDataMap.Keys)
- {
- if(clientFormDataMap.ContainsKey(datasetName))
- {
- var dataset = NewDataset(context, autofillFields, clientFormDataMap[datasetName], datasetAuth);
- if(dataset != null)
- {
- responseBuilder.AddDataset(dataset);
- }
- }
- }
- }
-
- if(autofillFields.SaveType != SaveDataType.Generic)
- {
- responseBuilder.SetSaveInfo(
- new SaveInfo.Builder(autofillFields.SaveType, autofillFields.AutofillIds.ToArray()).Build());
- return responseBuilder.Build();
- }
- else
- {
- //Log.d(TAG, "These fields are not meant to be saved by autofill.");
- return null;
- }
- }
-
- public static string[] FilterForSupportedHints(string[] hints)
- {
- if((hints?.Length ?? 0) == 0)
- {
- return new string[0];
- }
-
- 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;
- }
-
- public static bool IsValidHint(string hint)
- {
- switch(hint)
- {
- case View.AutofillHintCreditCardExpirationDate:
- case View.AutofillHintCreditCardExpirationDay:
- case View.AutofillHintCreditCardExpirationMonth:
- case View.AutofillHintCreditCardExpirationYear:
- case View.AutofillHintCreditCardNumber:
- case View.AutofillHintCreditCardSecurityCode:
- case View.AutofillHintEmailAddress:
- case View.AutofillHintPhone:
- case View.AutofillHintName:
- case View.AutofillHintPassword:
- case View.AutofillHintPostalAddress:
- case View.AutofillHintPostalCode:
- case View.AutofillHintUsername:
- return true;
- default:
- return false;
- }
- }
- }
-}
\ No newline at end of file