diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index ee5cd9f7a..ab71b1e74 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -300,6 +300,7 @@
+
@@ -307,8 +308,6 @@
-
-
diff --git a/src/Android/Autofill/AuthActivity.cs b/src/Android/Autofill/AuthActivity.cs
index f08fceb6b..f177cb233 100644
--- a/src/Android/Autofill/AuthActivity.cs
+++ b/src/Android/Autofill/AuthActivity.cs
@@ -1,13 +1,9 @@
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.Views;
using Android.Widget;
using Android.Support.V7.App;
using Android.Views.Autofill;
@@ -92,7 +88,7 @@ namespace Bit.Android.Autofill
}
var parser = new Parser(structure);
- parser.ParseForFill();
+ parser.Parse();
if(!parser.FieldCollection.Fields.Any() || string.IsNullOrWhiteSpace(parser.Uri))
{
_replyIntent = null;
diff --git a/src/Android/Autofill/AutofillService.cs b/src/Android/Autofill/AutofillService.cs
index 395aaaf65..cadf5dcee 100644
--- a/src/Android/Autofill/AutofillService.cs
+++ b/src/Android/Autofill/AutofillService.cs
@@ -4,6 +4,8 @@ using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Service.Autofill;
+using Android.Widget;
+using Bit.App;
using Bit.App.Abstractions;
using Bit.App.Enums;
using System.Linq;
@@ -29,7 +31,7 @@ namespace Bit.Android.Autofill
}
var parser = new Parser(structure);
- parser.ParseForFill();
+ parser.Parse();
if(!parser.FieldCollection.Fields.Any() || string.IsNullOrWhiteSpace(parser.Uri) ||
parser.Uri == "androidapp://com.x8bit.bitwarden" || parser.Uri == "androidapp://android")
@@ -70,16 +72,32 @@ namespace Bit.Android.Autofill
}
var parser = new Parser(structure);
- parser.ParseForSave();
+ parser.Parse();
+
+ var savedItem = parser.FieldCollection.GetSavedItem();
+ if(savedItem == null)
+ {
+ Toast.MakeText(this, "Unable to save this form.", ToastLength.Short).Show();
+ return;
+ }
var intent = new Intent(this, typeof(MainActivity));
+ intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTop);
intent.PutExtra("autofillFramework", true);
intent.PutExtra("autofillFrameworkSave", true);
- intent.PutExtra("autofillFrameworkType", (int)CipherType.Login);
- intent.PutExtra("autofillFrameworkUri", parser.Uri);
- intent.PutExtra("autofillFrameworkUsername", "username");
- intent.PutExtra("autofillFrameworkPassword", "pass123");
- intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTop);
+ intent.PutExtra("autofillFrameworkType", (int)savedItem.Type);
+ switch(savedItem.Type)
+ {
+ case CipherType.Login:
+ intent.PutExtra("autofillFrameworkName", parser.Uri.Replace(Constants.AndroidAppProtocol, string.Empty));
+ intent.PutExtra("autofillFrameworkUri", parser.Uri);
+ intent.PutExtra("autofillFrameworkUsername", savedItem.Login.Username);
+ intent.PutExtra("autofillFrameworkPassword", savedItem.Login.Password);
+ break;
+ default:
+ Toast.MakeText(this, "Unable to save this type of form.", ToastLength.Short).Show();
+ return;
+ }
StartActivity(intent);
}
}
diff --git a/src/Android/Autofill/CipherFilledItem.cs b/src/Android/Autofill/CipherFilledItem.cs
index f9ae98696..56f4731c2 100644
--- a/src/Android/Autofill/CipherFilledItem.cs
+++ b/src/Android/Autofill/CipherFilledItem.cs
@@ -54,6 +54,11 @@ namespace Bit.Android.Autofill
public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder)
{
+ if(!fieldCollection?.Fields.Any() ?? true)
+ {
+ return false;
+ }
+
if(Type == CipherType.Login)
{
var passwordField = fieldCollection.Fields.FirstOrDefault(
diff --git a/src/Android/Autofill/Field.cs b/src/Android/Autofill/Field.cs
index 198aa61c6..e83b4a43e 100644
--- a/src/Android/Autofill/Field.cs
+++ b/src/Android/Autofill/Field.cs
@@ -25,6 +25,26 @@ namespace Bit.Android.Autofill
Visible = node.Visibility == ViewStates.Visible;
Hints = AutofillHelpers.FilterForSupportedHints(node.GetAutofillHints());
AutofillOptions = node.GetAutofillOptions()?.ToList();
+
+ if(node.AutofillValue != null)
+ {
+ if(node.AutofillValue.IsList)
+ {
+ var autofillOptions = node.GetAutofillOptions();
+ if(autofillOptions != null && autofillOptions.Length > 0)
+ {
+ TextValue = autofillOptions[node.AutofillValue.ListValue];
+ }
+ }
+ else if(node.AutofillValue.IsDate)
+ {
+ DateValue = node.AutofillValue.DateValue;
+ }
+ else if(node.AutofillValue.IsText)
+ {
+ TextValue = node.AutofillValue.TextValue;
+ }
+ }
}
public SaveDataType SaveType { get; set; } = SaveDataType.Generic;
@@ -47,6 +67,9 @@ namespace Bit.Android.Autofill
public bool Clickable { get; private set; }
public bool Visible { get; private set; }
public List AutofillOptions { get; set; }
+ public string TextValue { get; set; }
+ public long? DateValue { get; set; }
+ public bool? ToggleValue { get; set; }
public int GetAutofillOptionIndex(string value)
{
@@ -106,5 +129,44 @@ namespace Bit.Android.Autofill
}
}
}
+
+ public bool ValueIsNull()
+ {
+ return TextValue == null && DateValue == null && ToggleValue == null;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if(this == obj)
+ {
+ return true;
+ }
+
+ if(obj == null || GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ var field = obj as Field;
+ if(TextValue != null ? !TextValue.Equals(field.TextValue) : field.TextValue != null)
+ {
+ return false;
+ }
+
+ if(DateValue != null ? !DateValue.Equals(field.DateValue) : field.DateValue != null)
+ {
+ return false;
+ }
+
+ return ToggleValue != null ? ToggleValue.Equals(field.ToggleValue) : field.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/FieldCollection.cs b/src/Android/Autofill/FieldCollection.cs
index 632b294f0..46c667c89 100644
--- a/src/Android/Autofill/FieldCollection.cs
+++ b/src/Android/Autofill/FieldCollection.cs
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using Android.Service.Autofill;
using Android.Views.Autofill;
+using System.Linq;
+using Android.Text;
namespace Bit.Android.Autofill
{
@@ -49,5 +51,43 @@ namespace Bit.Android.Autofill
}
}
}
+
+ public SavedItem GetSavedItem()
+ {
+ if(!Fields?.Any() ?? true)
+ {
+ return null;
+ }
+
+ var passwordField = Fields.FirstOrDefault(
+ f => f.InputType.HasFlag(InputTypes.TextVariationPassword) && !string.IsNullOrWhiteSpace(f.TextValue));
+ if(passwordField == null)
+ {
+ passwordField = Fields.FirstOrDefault(
+ f => (f.IdEntry?.ToLower().Contains("password") ?? false) && !string.IsNullOrWhiteSpace(f.TextValue));
+ }
+
+ if(passwordField == null)
+ {
+ return null;
+ }
+
+ var savedItem = new SavedItem
+ {
+ Type = App.Enums.CipherType.Login,
+ Login = new SavedItem.LoginItem
+ {
+ Password = passwordField.TextValue
+ }
+ };
+
+ var usernameField = Fields.TakeWhile(f => f.Id != passwordField.Id).LastOrDefault();
+ if(usernameField != null && !string.IsNullOrWhiteSpace(usernameField.TextValue))
+ {
+ savedItem.Login.Username = usernameField.TextValue;
+ }
+
+ return savedItem;
+ }
}
}
\ No newline at end of file
diff --git a/src/Android/Autofill/FilledField.cs b/src/Android/Autofill/FilledField.cs
deleted file mode 100644
index deffaddf0..000000000
--- a/src/Android/Autofill/FilledField.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System.Collections.Generic;
-using static Android.App.Assist.AssistStructure;
-
-namespace Bit.Android.Autofill
-{
- public class FilledField
- {
- public FilledField() { }
-
- public FilledField(ViewNode node)
- {
- Hints = AutofillHelpers.FilterForSupportedHints(node.GetAutofillHints());
-
- if(node.AutofillValue == null)
- {
- return;
- }
-
- if(node.AutofillValue.IsList)
- {
- var autofillOptions = node.GetAutofillOptions();
- if(autofillOptions != null && autofillOptions.Length > 0)
- {
- TextValue = autofillOptions[node.AutofillValue.ListValue];
- }
- }
- else if(node.AutofillValue.IsDate)
- {
- DateValue = node.AutofillValue.DateValue;
- }
- else if(node.AutofillValue.IsText)
- {
- TextValue = node.AutofillValue.TextValue;
- }
- }
-
- public string TextValue { get; set; }
- public long? DateValue { get; set; }
- public bool? ToggleValue { get; set; }
- public List Hints { get; set; }
-
- 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 field = o as FilledField;
- if(TextValue != null ? !TextValue.Equals(field.TextValue) : field.TextValue != null)
- {
- return false;
- }
-
- if(DateValue != null ? !DateValue.Equals(field.DateValue) : field.DateValue != null)
- {
- return false;
- }
-
- return ToggleValue != null ? ToggleValue.Equals(field.ToggleValue) : field.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/FilledFieldCollection.cs b/src/Android/Autofill/FilledFieldCollection.cs
deleted file mode 100644
index 4b0d57796..000000000
--- a/src/Android/Autofill/FilledFieldCollection.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-using System;
-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 FilledFieldCollection : IFilledItem
- {
- public FilledFieldCollection()
- : this(null, new Dictionary())
- { }
-
- public FilledFieldCollection(string datasetName, IDictionary hintMap)
- {
- HintToFieldMap = hintMap;
- Name = datasetName;
- Subtitle = "subtitle";
- Icon = Resource.Drawable.login;
- }
-
- public IDictionary HintToFieldMap { get; private set; }
- public string Name { get; set; }
- public string Subtitle { get; set; }
- public int Icon { get; set; }
-
- public void Add(FilledField filledField)
- {
- if(filledField == null)
- {
- throw new ArgumentNullException(nameof(filledField));
- }
-
- foreach(var hint in filledField.Hints)
- {
- HintToFieldMap.Add(hint, filledField);
- }
- }
-
- public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder)
- {
- var setValue = false;
- foreach(var hint in fieldCollection.Hints)
- {
- if(!fieldCollection.HintToFieldsMap.ContainsKey(hint))
- {
- continue;
- }
-
- var fillableFields = fieldCollection.HintToFieldsMap[hint];
- for(var i = 0; i < fillableFields.Count; i++)
- {
- if(!HintToFieldMap.ContainsKey(hint))
- {
- continue;
- }
-
- var field = fillableFields[i];
- var filledField = HintToFieldMap[hint];
-
- switch(field.AutofillType)
- {
- case AutofillType.List:
- int listValue = field.GetAutofillOptionIndex(filledField.TextValue);
- if(listValue != -1)
- {
- datasetBuilder.SetValue(field.AutofillId, AutofillValue.ForList(listValue));
- setValue = true;
- }
- break;
- case AutofillType.Date:
- var dateValue = filledField.DateValue;
- if(dateValue != null)
- {
- datasetBuilder.SetValue(field.AutofillId, AutofillValue.ForDate(dateValue.Value));
- setValue = true;
- }
- break;
- case AutofillType.Text:
- var textValue = filledField.TextValue;
- if(textValue != null)
- {
- datasetBuilder.SetValue(field.AutofillId, AutofillValue.ForText(textValue));
- setValue = true;
- }
- break;
- case AutofillType.Toggle:
- var toggleValue = filledField.ToggleValue;
- if(toggleValue != null)
- {
- datasetBuilder.SetValue(field.AutofillId, AutofillValue.ForToggle(toggleValue.Value));
- setValue = true;
- }
- break;
- case AutofillType.None:
- default:
- break;
- }
- }
- }
-
- return setValue;
- }
-
- public bool HelpsWithHints(List autofillHints)
- {
- return autofillHints.Any(h => HintToFieldMap.ContainsKey(h) && !HintToFieldMap[h].IsNull());
- }
- }
-}
\ No newline at end of file
diff --git a/src/Android/Autofill/Parser.cs b/src/Android/Autofill/Parser.cs
index 3deadb8b0..2ff0046e5 100644
--- a/src/Android/Autofill/Parser.cs
+++ b/src/Android/Autofill/Parser.cs
@@ -1,5 +1,6 @@
using static Android.App.Assist.AssistStructure;
using Android.App.Assist;
+using Bit.App;
namespace Bit.Android.Autofill
{
@@ -14,7 +15,6 @@ namespace Bit.Android.Autofill
}
public FieldCollection FieldCollection { get; private set; } = new FieldCollection();
- public FilledFieldCollection FilledFieldCollection { get; private set; } = new FilledFieldCollection();
public string Uri
{
get => _uri;
@@ -26,30 +26,20 @@ namespace Bit.Android.Autofill
return;
}
- _uri = $"androidapp://{value}";
+ _uri = string.Concat(Constants.AndroidAppProtocol, value);
}
}
- public void ParseForFill()
- {
- Parse(true);
- }
-
- public void ParseForSave()
- {
- Parse(false);
- }
-
- private void Parse(bool forFill)
+ public void Parse()
{
for(var i = 0; i < _structure.WindowNodeCount; i++)
{
var node = _structure.GetWindowNodeAt(i);
- ParseNode(forFill, node.RootViewNode);
+ ParseNode(node.RootViewNode);
}
}
- private void ParseNode(bool forFill, ViewNode node)
+ private void ParseNode(ViewNode node)
{
var hints = node.GetAutofillHints();
var isEditText = node.ClassName == "android.widget.EditText";
@@ -59,20 +49,12 @@ namespace Bit.Android.Autofill
{
Uri = node.IdPackage;
}
-
- if(forFill)
- {
- FieldCollection.Add(new Field(node));
- }
- else
- {
- FilledFieldCollection.Add(new FilledField(node));
- }
+ FieldCollection.Add(new Field(node));
}
for(var i = 0; i < node.ChildCount; i++)
{
- ParseNode(forFill, node.GetChildAt(i));
+ ParseNode(node.GetChildAt(i));
}
}
}
diff --git a/src/Android/Autofill/SavedItem.cs b/src/Android/Autofill/SavedItem.cs
new file mode 100644
index 000000000..280335199
--- /dev/null
+++ b/src/Android/Autofill/SavedItem.cs
@@ -0,0 +1,26 @@
+using Bit.App.Enums;
+
+namespace Bit.Android.Autofill
+{
+ public class SavedItem
+ {
+ public CipherType Type { get; set; }
+ public LoginItem Login { get; set; }
+ public CardItem Card { get; set; }
+
+ public class LoginItem
+ {
+ public string Username { get; set; }
+ public string Password { get; set; }
+ }
+
+ public class CardItem
+ {
+ public string Name { get; set; }
+ public string Number { get; set; }
+ public string ExpMonth { get; set; }
+ public string ExpYear { get; set; }
+ public string Code { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs
index b7754b8b3..4816b4fe8 100644
--- a/src/Android/MainActivity.cs
+++ b/src/Android/MainActivity.cs
@@ -161,7 +161,7 @@ namespace Bit.Android
}
var parser = new Parser(structure);
- parser.ParseForFill();
+ parser.Parse();
if(!parser.FieldCollection.Fields.Any() || string.IsNullOrWhiteSpace(parser.Uri))
{
SetResult(Result.Canceled);
@@ -438,6 +438,7 @@ namespace Bit.Android
if(Intent.GetBooleanExtra("autofillFrameworkSave", false))
{
options.SaveType = (CipherType)Intent.GetIntExtra("autofillFrameworkType", 0);
+ options.SaveName = Intent.GetStringExtra("autofillFrameworkName");
options.SaveUsername = Intent.GetStringExtra("autofillFrameworkUsername");
options.SavePassword = Intent.GetStringExtra("autofillFrameworkPassword");
}
diff --git a/src/App/Models/AppOptions.cs b/src/App/Models/AppOptions.cs
index cbfe7efa5..221bf4320 100644
--- a/src/App/Models/AppOptions.cs
+++ b/src/App/Models/AppOptions.cs
@@ -8,6 +8,7 @@ namespace Bit.App.Models
public bool FromAutofillFramework { get; set; }
public string Uri { get; set; }
public CipherType? SaveType { get; set; }
+ public string SaveName { get; set; }
public string SaveUsername { get; set; }
public string SavePassword { get; set; }
}
diff --git a/src/App/Pages/Vault/VaultAddCipherPage.cs b/src/App/Pages/Vault/VaultAddCipherPage.cs
index 37bf0f786..43d711f40 100644
--- a/src/App/Pages/Vault/VaultAddCipherPage.cs
+++ b/src/App/Pages/Vault/VaultAddCipherPage.cs
@@ -38,7 +38,7 @@ namespace Bit.App.Pages
private DateTime? _lastAction;
public VaultAddCipherPage(AppOptions options)
- : this(options.SaveType.Value, options.Uri, options.Uri, options.FromAutofillFramework, false)
+ : this(options.SaveType.Value, options.Uri, options.SaveName, options.FromAutofillFramework, false)
{
_defaultUsername = options.SaveUsername;
_defaultPassword = options.SavePassword;