From caff67b77d3c9ea0b073927c3a37c6fa77ca72bf Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Sat, 18 Nov 2017 23:04:21 -0500 Subject: [PATCH] added cards and other improvements to save --- src/Android/Android.csproj | 3 +- src/Android/Autofill/AutofillHelpers.cs | 24 +- src/Android/Autofill/AutofillService.cs | 10 +- src/Android/Autofill/CipherFilledItem.cs | 146 ----------- src/Android/Autofill/Field.cs | 22 +- src/Android/Autofill/FieldCollection.cs | 120 ++++++++- src/Android/Autofill/FilledItem.cs | 291 ++++++++++++++++++++++ src/Android/Autofill/IFilledItem.cs | 13 - src/Android/MainActivity.cs | 7 +- src/App/Models/AppOptions.cs | 5 + src/App/Pages/Vault/VaultAddCipherPage.cs | 35 ++- 11 files changed, 477 insertions(+), 199 deletions(-) delete mode 100644 src/Android/Autofill/CipherFilledItem.cs create mode 100644 src/Android/Autofill/FilledItem.cs delete mode 100644 src/Android/Autofill/IFilledItem.cs diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index 3858e3023..f8a4d826d 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -291,8 +291,7 @@ - - + diff --git a/src/Android/Autofill/AutofillHelpers.cs b/src/Android/Autofill/AutofillHelpers.cs index 32ae0ce4e..5e4c38613 100644 --- a/src/Android/Autofill/AutofillHelpers.cs +++ b/src/Android/Autofill/AutofillHelpers.cs @@ -13,9 +13,9 @@ namespace Bit.Android.Autofill { public static class AutofillHelpers { - public static async Task> GetFillItemsAsync(Parser parser, ICipherService service) + public static async Task> GetFillItemsAsync(Parser parser, ICipherService service) { - var items = new List(); + var items = new List(); if(parser.FieldCollection.FillableForLogin) { @@ -26,7 +26,7 @@ namespace Bit.Android.Autofill allCiphers.AddRange(ciphers.Item2.ToList()); foreach(var cipher in allCiphers) { - items.Add(new CipherFilledItem(cipher)); + items.Add(new FilledItem(cipher)); } } } @@ -35,14 +35,14 @@ namespace Bit.Android.Autofill var ciphers = await service.GetAllAsync(); foreach(var cipher in ciphers.Where(c => c.Type == App.Enums.CipherType.Card)) { - items.Add(new CipherFilledItem(cipher)); + items.Add(new FilledItem(cipher)); } } return items; } - public static FillResponse BuildFillResponse(Context context, Parser parser, List items) + public static FillResponse BuildFillResponse(Context context, Parser parser, List items) { var responseBuilder = new FillResponse.Builder(); if(items != null && items.Count > 0) @@ -62,7 +62,7 @@ namespace Bit.Android.Autofill return responseBuilder.Build(); } - public static Dataset BuildDataset(Context context, FieldCollection fields, IFilledItem filledItem) + public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem) { var datasetBuilder = new Dataset.Builder( BuildListView(context.PackageName, filledItem.Name, filledItem.Subtitle, filledItem.Icon)); @@ -100,13 +100,19 @@ namespace Bit.Android.Autofill public static void AddSaveInfo(FillResponse.Builder responseBuilder, FieldCollection fields) { var saveType = fields.SaveType; - if(saveType == SaveDataType.Generic) + var requiredIds = fields.GetRequiredSaveFields(); + if(saveType == SaveDataType.Generic || requiredIds.Length == 0) { return; } - var saveInfo = new SaveInfo.Builder(saveType, fields.AutofillIds.ToArray()).Build(); - responseBuilder.SetSaveInfo(saveInfo); + var saveBuilder = new SaveInfo.Builder(saveType, requiredIds); + var optionalIds = fields.GetOptionalSaveIds(); + if(optionalIds.Length > 0) + { + saveBuilder.SetOptionalIds(optionalIds); + } + responseBuilder.SetSaveInfo(saveBuilder.Build()); } } } \ No newline at end of file diff --git a/src/Android/Autofill/AutofillService.cs b/src/Android/Autofill/AutofillService.cs index 05cc7059d..9e7bfc651 100644 --- a/src/Android/Autofill/AutofillService.cs +++ b/src/Android/Autofill/AutofillService.cs @@ -34,8 +34,7 @@ namespace Bit.Android.Autofill parser.Parse(); if(string.IsNullOrWhiteSpace(parser.Uri) || parser.Uri == "androidapp://com.x8bit.bitwarden" || - parser.Uri == "androidapp://android" || - (!parser.FieldCollection.FillableForLogin && !parser.FieldCollection.FillableForCard)) + parser.Uri == "androidapp://android" || !parser.FieldCollection.Fillable) { return; } @@ -95,6 +94,13 @@ namespace Bit.Android.Autofill intent.PutExtra("autofillFrameworkUsername", savedItem.Login.Username); intent.PutExtra("autofillFrameworkPassword", savedItem.Login.Password); break; + case CipherType.Card: + intent.PutExtra("autofillFrameworkCardName", savedItem.Card.Name); + intent.PutExtra("autofillFrameworkCardNumber", savedItem.Card.Number); + intent.PutExtra("autofillFrameworkCardExpMonth", savedItem.Card.ExpMonth); + intent.PutExtra("autofillFrameworkCardExpYear", savedItem.Card.ExpYear); + intent.PutExtra("autofillFrameworkCardCode", savedItem.Card.Code); + break; default: Toast.MakeText(this, "Unable to save this type of form.", ToastLength.Short).Show(); return; diff --git a/src/Android/Autofill/CipherFilledItem.cs b/src/Android/Autofill/CipherFilledItem.cs deleted file mode 100644 index b94d31d2d..000000000 --- a/src/Android/Autofill/CipherFilledItem.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using Android.Service.Autofill; -using Android.Views.Autofill; -using System.Linq; -using Bit.App.Models; -using Bit.App.Enums; -using Bit.App.Models.Page; -using Android.Views; - -namespace Bit.Android.Autofill -{ - public class CipherFilledItem : IFilledItem - { - private Lazy _password; - private string _cardNumber; - private Lazy _cardExpMonth; - private Lazy _cardExpYear; - private Lazy _cardCode; - - public CipherFilledItem(Cipher cipher) - { - Name = cipher.Name?.Decrypt() ?? "--"; - Type = cipher.Type; - - switch(Type) - { - case CipherType.Login: - Subtitle = cipher.Login.Username?.Decrypt() ?? string.Empty; - Icon = Resource.Drawable.login; - _password = new Lazy(() => cipher.Login.Password?.Decrypt()); - break; - case CipherType.Card: - Subtitle = cipher.Card.Brand?.Decrypt(); - _cardNumber = cipher.Card.Number?.Decrypt(); - if(!string.IsNullOrWhiteSpace(_cardNumber) && _cardNumber.Length >= 4) - { - if(!string.IsNullOrWhiteSpace(_cardNumber)) - { - Subtitle += ", "; - } - Subtitle += ("*" + _cardNumber.Substring(_cardNumber.Length - 4)); - } - Icon = Resource.Drawable.card; - _cardCode = new Lazy(() => cipher.Card.Code?.Decrypt()); - _cardExpMonth = new Lazy(() => cipher.Card.ExpMonth?.Decrypt()); - _cardExpYear = new Lazy(() => cipher.Card.ExpYear?.Decrypt()); - break; - default: - break; - } - } - - public CipherFilledItem(VaultListPageModel.Cipher cipher) - { - Name = cipher.Name ?? "--"; - Type = cipher.Type; - - switch(Type) - { - case CipherType.Login: - Subtitle = cipher.LoginUsername ?? string.Empty; - _password = cipher.LoginPassword; - Icon = Resource.Drawable.login; - break; - default: - break; - } - } - - public string Name { get; set; } - public string Subtitle { get; set; } = string.Empty; - public int Icon { get; set; } = Resource.Drawable.login; - public CipherType Type { get; set; } - - public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder) - { - if(!fieldCollection?.Fields.Any() ?? true) - { - return false; - } - - var setValues = false; - if(Type == CipherType.Login) - { - if(fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password.Value)) - { - foreach(var f in fieldCollection.PasswordFields) - { - setValues = true; - datasetBuilder.SetValue(f.AutofillId, AutofillValue.ForText(_password.Value)); - } - } - - if(fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle)) - { - foreach(var f in fieldCollection.UsernameFields) - { - setValues = true; - datasetBuilder.SetValue(f.AutofillId, AutofillValue.ForText(Subtitle)); - } - } - } - else if(Type == CipherType.Card) - { - if(fieldCollection.HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardNumber) && - !string.IsNullOrWhiteSpace(_cardNumber)) - { - foreach(var f in fieldCollection.HintToFieldsMap[View.AutofillHintCreditCardNumber]) - { - setValues = true; - datasetBuilder.SetValue(f.AutofillId, AutofillValue.ForText(_cardNumber)); - } - } - if(fieldCollection.HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardSecurityCode) && - !string.IsNullOrWhiteSpace(_cardCode.Value)) - { - foreach(var f in fieldCollection.HintToFieldsMap[View.AutofillHintCreditCardSecurityCode]) - { - setValues = true; - datasetBuilder.SetValue(f.AutofillId, AutofillValue.ForText(_cardCode.Value)); - } - } - if(fieldCollection.HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationMonth) && - !string.IsNullOrWhiteSpace(_cardExpMonth.Value)) - { - foreach(var f in fieldCollection.HintToFieldsMap[View.AutofillHintCreditCardExpirationMonth]) - { - setValues = true; - datasetBuilder.SetValue(f.AutofillId, AutofillValue.ForText(_cardExpMonth.Value)); - } - } - if(fieldCollection.HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationYear) && - !string.IsNullOrWhiteSpace(_cardExpYear.Value)) - { - foreach(var f in fieldCollection.HintToFieldsMap[View.AutofillHintCreditCardExpirationYear]) - { - setValues = true; - datasetBuilder.SetValue(f.AutofillId, AutofillValue.ForText(_cardExpYear.Value)); - } - } - } - - return setValues; - } - } -} \ No newline at end of file diff --git a/src/Android/Autofill/Field.cs b/src/Android/Autofill/Field.cs index 9353a5b17..7e53e04d0 100644 --- a/src/Android/Autofill/Field.cs +++ b/src/Android/Autofill/Field.cs @@ -33,6 +33,7 @@ namespace Bit.Android.Autofill var autofillOptions = node.GetAutofillOptions(); if(autofillOptions != null && autofillOptions.Length > 0) { + ListValue = node.AutofillValue.ListValue; TextValue = autofillOptions[node.AutofillValue.ListValue]; } } @@ -44,6 +45,10 @@ namespace Bit.Android.Autofill { TextValue = node.AutofillValue.TextValue; } + else if(node.AutofillValue.IsToggle) + { + ToggleValue = node.AutofillValue.ToggleValue; + } } } @@ -69,24 +74,9 @@ namespace Bit.Android.Autofill public List AutofillOptions { get; set; } public string TextValue { get; set; } public long? DateValue { get; set; } + public int? ListValue { get; set; } public bool? ToggleValue { get; set; } - public int GetAutofillOptionIndex(string value) - { - if(AutofillOptions != null) - { - for(var i = 0; i < AutofillOptions.Count; i++) - { - if(AutofillOptions[i].Equals(value)) - { - return i; - } - } - } - - return -1; - } - private void UpdateSaveTypeFromHints() { SaveType = SaveDataType.Generic; diff --git a/src/Android/Autofill/FieldCollection.cs b/src/Android/Autofill/FieldCollection.cs index 6cdfc1fc6..6f02c713c 100644 --- a/src/Android/Autofill/FieldCollection.cs +++ b/src/Android/Autofill/FieldCollection.cs @@ -58,7 +58,12 @@ namespace Bit.Android.Autofill } else { - _passwordFields = Fields.Where(f => f.InputType.HasFlag(InputTypes.TextVariationPassword)).ToList(); + _passwordFields = Fields + .Where(f => + f.InputType.HasFlag(InputTypes.TextVariationPassword) || + f.InputType.HasFlag(InputTypes.TextVariationVisiblePassword) || + f.InputType.HasFlag(InputTypes.TextVariationWebPassword)) + .ToList(); if(!_passwordFields.Any()) { _passwordFields = Fields.Where(f => f.IdEntry?.ToLower().Contains("password") ?? false).ToList(); @@ -116,6 +121,8 @@ namespace Bit.Android.Autofill new string[] { View.AutofillHintName, View.AutofillHintPhone, View.AutofillHintPostalAddress, View.AutofillHintPostalCode }); + public bool Fillable => FillableForLogin || FillableForCard || FillableForIdentity; + public void Add(Field field) { if(Ids.Contains(field.Id)) @@ -165,15 +172,12 @@ namespace Bit.Android.Autofill Type = App.Enums.CipherType.Login, Login = new SavedItem.LoginItem { - Password = passwordField.TextValue + Password = GetFieldValue(passwordField) } }; var usernameField = Fields.TakeWhile(f => f.Id != passwordField.Id).LastOrDefault(); - if(usernameField != null && !string.IsNullOrWhiteSpace(usernameField.TextValue)) - { - savedItem.Login.Username = usernameField.TextValue; - } + savedItem.Login.Username = GetFieldValue(usernameField); return savedItem; } @@ -184,9 +188,11 @@ namespace Bit.Android.Autofill Type = App.Enums.CipherType.Card, Card = new SavedItem.CardItem { - Number = HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardNumber) ? - HintToFieldsMap[View.AutofillHintCreditCardNumber].FirstOrDefault( - f => !string.IsNullOrWhiteSpace(f.TextValue))?.TextValue : null + Number = GetFieldValue(View.AutofillHintCreditCardNumber), + Name = GetFieldValue(View.AutofillHintName), + ExpMonth = GetFieldValue(View.AutofillHintCreditCardExpirationMonth, true), + ExpYear = GetFieldValue(View.AutofillHintCreditCardExpirationYear), + Code = GetFieldValue(View.AutofillHintCreditCardSecurityCode) } }; @@ -196,9 +202,105 @@ namespace Bit.Android.Autofill return null; } + public AutofillId[] GetOptionalSaveIds() + { + if(SaveType == SaveDataType.Password) + { + return UsernameFields.Select(f => f.AutofillId).ToArray(); + } + else if(SaveType == SaveDataType.CreditCard) + { + var fieldList = new List(); + if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardSecurityCode)) + { + fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardSecurityCode]); + } + if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationYear)) + { + fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardExpirationYear]); + } + if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationMonth)) + { + fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardExpirationMonth]); + } + if(HintToFieldsMap.ContainsKey(View.AutofillHintName)) + { + fieldList.AddRange(HintToFieldsMap[View.AutofillHintName]); + } + return fieldList.Select(f => f.AutofillId).ToArray(); + } + + return new AutofillId[0]; + } + + public AutofillId[] GetRequiredSaveFields() + { + if(SaveType == SaveDataType.Password) + { + return PasswordFields.Select(f => f.AutofillId).ToArray(); + } + else if(SaveType == SaveDataType.CreditCard && HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardNumber)) + { + return HintToFieldsMap[View.AutofillHintCreditCardNumber].Select(f => f.AutofillId).ToArray(); + } + + return new AutofillId[0]; + } + private bool FocusedHintsContain(IEnumerable hints) { return hints.Any(h => FocusedHints.Contains(h)); } + + private string GetFieldValue(string hint, bool monthValue = false) + { + if(HintToFieldsMap.ContainsKey(hint)) + { + foreach(var field in HintToFieldsMap[hint]) + { + var val = GetFieldValue(field, monthValue); + if(!string.IsNullOrWhiteSpace(val)) + { + return val; + } + } + } + + return null; + } + + private string GetFieldValue(Field field, bool monthValue = false) + { + if(field == null) + { + return null; + } + + if(!string.IsNullOrWhiteSpace(field.TextValue)) + { + if(field.AutofillType == AutofillType.List && field.ListValue.HasValue && monthValue) + { + if(field.AutofillOptions.Count == 13) + { + return field.ListValue.ToString(); + } + else if(field.AutofillOptions.Count == 12) + { + return (field.ListValue + 1).ToString(); + } + } + return field.TextValue; + } + else if(field.DateValue.HasValue) + { + return field.DateValue.Value.ToString(); + } + else if(field.ToggleValue.HasValue) + { + return field.ToggleValue.Value.ToString(); + } + + return null; + } } } \ No newline at end of file diff --git a/src/Android/Autofill/FilledItem.cs b/src/Android/Autofill/FilledItem.cs new file mode 100644 index 000000000..878a0bf22 --- /dev/null +++ b/src/Android/Autofill/FilledItem.cs @@ -0,0 +1,291 @@ +using System; +using Android.Service.Autofill; +using Android.Views.Autofill; +using System.Linq; +using Bit.App.Models; +using Bit.App.Enums; +using Bit.App.Models.Page; +using Android.Views; + +namespace Bit.Android.Autofill +{ + public class FilledItem + { + private Lazy _password; + private Lazy _cardName; + private string _cardNumber; + private Lazy _cardExpMonth; + private Lazy _cardExpYear; + private Lazy _cardCode; + private Lazy _idPhone; + private Lazy _idEmail; + private Lazy _idUsername; + private Lazy _idAddress; + private Lazy _idPostalCode; + + public FilledItem(Cipher cipher) + { + Name = cipher.Name?.Decrypt(cipher.OrganizationId) ?? "--"; + Type = cipher.Type; + + switch(Type) + { + case CipherType.Login: + Subtitle = cipher.Login.Username?.Decrypt(cipher.OrganizationId) ?? string.Empty; + Icon = Resource.Drawable.login; + _password = new Lazy(() => cipher.Login.Password?.Decrypt(cipher.OrganizationId)); + break; + case CipherType.Card: + Subtitle = cipher.Card.Brand?.Decrypt(cipher.OrganizationId); + _cardNumber = cipher.Card.Number?.Decrypt(cipher.OrganizationId); + if(!string.IsNullOrWhiteSpace(_cardNumber) && _cardNumber.Length >= 4) + { + if(!string.IsNullOrWhiteSpace(_cardNumber)) + { + Subtitle += ", "; + } + Subtitle += ("*" + _cardNumber.Substring(_cardNumber.Length - 4)); + } + Icon = Resource.Drawable.card; + _cardName = new Lazy(() => cipher.Card.CardholderName?.Decrypt(cipher.OrganizationId)); + _cardCode = new Lazy(() => cipher.Card.Code?.Decrypt(cipher.OrganizationId)); + _cardExpMonth = new Lazy(() => cipher.Card.ExpMonth?.Decrypt(cipher.OrganizationId)); + _cardExpYear = new Lazy(() => cipher.Card.ExpYear?.Decrypt(cipher.OrganizationId)); + break; + case CipherType.Identity: + var firstName = cipher.Identity?.FirstName?.Decrypt(cipher.OrganizationId) ?? " "; + var lastName = cipher.Identity?.LastName?.Decrypt(cipher.OrganizationId) ?? " "; + Subtitle = " "; + if(!string.IsNullOrWhiteSpace(firstName)) + { + Subtitle = firstName; + } + if(!string.IsNullOrWhiteSpace(lastName)) + { + if(!string.IsNullOrWhiteSpace(Subtitle)) + { + Subtitle += " "; + } + Subtitle += lastName; + } + Icon = Resource.Drawable.id; + _idPhone = new Lazy(() => cipher.Identity.Phone?.Decrypt(cipher.OrganizationId)); + _idEmail = new Lazy(() => cipher.Identity.Email?.Decrypt(cipher.OrganizationId)); + _idUsername = new Lazy(() => cipher.Identity.Username?.Decrypt(cipher.OrganizationId)); + _idAddress = new Lazy(() => + { + var address = cipher.Identity.Address1?.Decrypt(cipher.OrganizationId); + + var address2 = cipher.Identity.Address2?.Decrypt(cipher.OrganizationId); + if(!string.IsNullOrWhiteSpace(address2)) + { + if(!string.IsNullOrWhiteSpace(address)) + { + address += ", "; + } + + address += address2; + } + + var address3 = cipher.Identity.Address3?.Decrypt(cipher.OrganizationId); + if(!string.IsNullOrWhiteSpace(address3)) + { + if(!string.IsNullOrWhiteSpace(address)) + { + address += ", "; + } + + address += address3; + } + + return address; + }); + _idPostalCode = new Lazy(() => cipher.Identity.PostalCode?.Decrypt(cipher.OrganizationId)); + break; + default: + break; + } + } + + public FilledItem(VaultListPageModel.Cipher cipher) + { + Name = cipher.Name ?? "--"; + Type = cipher.Type; + + switch(Type) + { + case CipherType.Login: + Subtitle = cipher.LoginUsername ?? string.Empty; + _password = cipher.LoginPassword; + Icon = Resource.Drawable.login; + break; + default: + break; + } + } + + public string Name { get; set; } + public string Subtitle { get; set; } = string.Empty; + public int Icon { get; set; } = Resource.Drawable.login; + public CipherType Type { get; set; } + + public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder) + { + if(!fieldCollection?.Fields.Any() ?? true) + { + return false; + } + + var setValues = false; + if(Type == CipherType.Login) + { + if(fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password.Value)) + { + foreach(var f in fieldCollection.PasswordFields) + { + var val = ApplyValue(f, _password.Value); + if(val != null) + { + setValues = true; + datasetBuilder.SetValue(f.AutofillId, val); + } + } + } + + if(fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle)) + { + foreach(var f in fieldCollection.UsernameFields) + { + var val = ApplyValue(f, Subtitle); + if(val != null) + { + setValues = true; + datasetBuilder.SetValue(f.AutofillId, val); + } + } + } + } + else if(Type == CipherType.Card) + { + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardNumber, + new Lazy(() => _cardNumber))) + { + setValues = true; + } + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardSecurityCode, _cardCode)) + { + setValues = true; + } + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardExpirationMonth, _cardExpMonth, true)) + { + setValues = true; + } + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardExpirationYear, _cardExpYear)) + { + setValues = true; + } + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintName, _cardName)) + { + setValues = true; + } + } + else if(Type == CipherType.Identity) + { + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPhone, _idPhone)) + { + setValues = true; + } + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintEmailAddress, _idEmail)) + { + setValues = true; + } + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintUsername, _idUsername)) + { + setValues = true; + } + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPostalAddress, _idAddress)) + { + setValues = true; + } + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPostalCode, _idPostalCode)) + { + setValues = true; + } + if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintName, new Lazy(() => Subtitle))) + { + setValues = true; + } + } + + return setValues; + } + + private static bool ApplyValue(Dataset.Builder builder, FieldCollection fieldCollection, + string hint, Lazy value, bool monthValue = false) + { + bool setValues = false; + if(fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value.Value)) + { + foreach(var f in fieldCollection.HintToFieldsMap[hint]) + { + var val = ApplyValue(f, value.Value, monthValue); + if(val != null) + { + setValues = true; + builder.SetValue(f.AutofillId, val); + } + } + } + return setValues; + } + + private static AutofillValue ApplyValue(Field field, string value, bool monthValue = false) + { + switch(field.AutofillType) + { + case AutofillType.Date: + if(long.TryParse(value, out long dateValue)) + { + return AutofillValue.ForDate(dateValue); + } + break; + case AutofillType.List: + if(field.AutofillOptions != null) + { + if(monthValue && int.TryParse(value, out int monthIndex)) + { + if(field.AutofillOptions.Count == 13) + { + return AutofillValue.ForList(monthIndex); + } + else if(field.AutofillOptions.Count >= monthIndex) + { + return AutofillValue.ForList(monthIndex - 1); + } + } + + for(var i = 0; i < field.AutofillOptions.Count; i++) + { + if(field.AutofillOptions[i].Equals(value)) + { + return AutofillValue.ForList(i); + } + } + } + break; + case AutofillType.Text: + return AutofillValue.ForText(value); + case AutofillType.Toggle: + if(bool.TryParse(value, out bool toggleValue)) + { + return AutofillValue.ForToggle(toggleValue); + } + break; + default: + break; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Android/Autofill/IFilledItem.cs b/src/Android/Autofill/IFilledItem.cs deleted file mode 100644 index ca41ee9c9..000000000 --- a/src/Android/Autofill/IFilledItem.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Android.Service.Autofill; -using System; - -namespace Bit.Android.Autofill -{ - public interface IFilledItem - { - string Name { get; set; } - string Subtitle { get; set; } - int Icon { get; set; } - bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder); - } -} \ No newline at end of file diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index 92d7375e8..dbe79b55d 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -169,7 +169,7 @@ namespace Bit.Android return; } - var items = new List { new CipherFilledItem(cipher) }; + var items = new List { new FilledItem(cipher) }; var response = AutofillHelpers.BuildFillResponse(this, parser, items); var replyIntent = new Intent(); replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, response); @@ -441,6 +441,11 @@ namespace Bit.Android options.SaveName = Intent.GetStringExtra("autofillFrameworkName"); options.SaveUsername = Intent.GetStringExtra("autofillFrameworkUsername"); options.SavePassword = Intent.GetStringExtra("autofillFrameworkPassword"); + options.SaveCardName = Intent.GetStringExtra("autofillFrameworkCardName"); + options.SaveCardNumber = Intent.GetStringExtra("autofillFrameworkCardNumber"); + options.SaveCardExpMonth = Intent.GetStringExtra("autofillFrameworkCardExpMonth"); + options.SaveCardExpYear = Intent.GetStringExtra("autofillFrameworkCardExpYear"); + options.SaveCardCode = Intent.GetStringExtra("autofillFrameworkCardCode"); } return options; diff --git a/src/App/Models/AppOptions.cs b/src/App/Models/AppOptions.cs index 221bf4320..a2461773d 100644 --- a/src/App/Models/AppOptions.cs +++ b/src/App/Models/AppOptions.cs @@ -11,5 +11,10 @@ namespace Bit.App.Models public string SaveName { get; set; } public string SaveUsername { get; set; } public string SavePassword { get; set; } + public string SaveCardName { get; set; } + public string SaveCardNumber { get; set; } + public string SaveCardExpMonth { get; set; } + public string SaveCardExpYear { get; set; } + public string SaveCardCode { get; set; } } } diff --git a/src/App/Pages/Vault/VaultAddCipherPage.cs b/src/App/Pages/Vault/VaultAddCipherPage.cs index 43d711f40..9826020d9 100644 --- a/src/App/Pages/Vault/VaultAddCipherPage.cs +++ b/src/App/Pages/Vault/VaultAddCipherPage.cs @@ -33,6 +33,11 @@ namespace Bit.App.Pages private readonly string _defaultName; private readonly string _defaultUsername; private readonly string _defaultPassword; + private readonly string _defaultCardName; + private readonly string _defaultCardNumber; + private readonly int? _defaultCardExpMonth; + private readonly string _defaultCardExpYear; + private readonly string _defaultCardCode; private readonly bool _fromAutofill; private readonly bool _fromAutofillFramework; private DateTime? _lastAction; @@ -40,9 +45,17 @@ namespace Bit.App.Pages public VaultAddCipherPage(AppOptions options) : this(options.SaveType.Value, options.Uri, options.SaveName, options.FromAutofillFramework, false) { + _fromAutofillFramework = options.FromAutofillFramework; _defaultUsername = options.SaveUsername; _defaultPassword = options.SavePassword; - _fromAutofillFramework = options.FromAutofillFramework; + _defaultCardCode = options.SaveCardCode; + if(int.TryParse(options.SaveCardExpMonth, out int month) && month <= 12 && month >= 1) + { + _defaultCardExpMonth = month; + } + _defaultCardExpYear = options.SaveCardExpYear; + _defaultCardName = options.SaveCardName; + _defaultCardNumber = options.SaveCardNumber; Init(); } @@ -410,19 +423,39 @@ namespace Bit.App.Pages { CardCodeCell = new FormEntryCell(AppResources.SecurityCode, Keyboard.Numeric, nextElement: NotesCell.Editor); + if(!string.IsNullOrWhiteSpace(_defaultCardCode)) + { + CardCodeCell.Entry.Text = _defaultCardCode; + } CardExpYearCell = new FormEntryCell(AppResources.ExpirationYear, Keyboard.Numeric, nextElement: CardCodeCell.Entry); + if(!string.IsNullOrWhiteSpace(_defaultCardExpYear)) + { + CardExpYearCell.Entry.Text = _defaultCardExpYear; + } CardExpMonthCell = new FormPickerCell(AppResources.ExpirationMonth, new string[] { "--", AppResources.January, AppResources.February, AppResources.March, AppResources.April, AppResources.May, AppResources.June, AppResources.July, AppResources.August, AppResources.September, AppResources.October, AppResources.November, AppResources.December }); + if(_defaultCardExpMonth.HasValue) + { + CardExpMonthCell.Picker.SelectedIndex = _defaultCardExpMonth.Value; + } CardBrandCell = new FormPickerCell(AppResources.Brand, new string[] { "--", "Visa", "Mastercard", "American Express", "Discover", "Diners Club", "JCB", "Maestro", "UnionPay", AppResources.Other }); CardNumberCell = new FormEntryCell(AppResources.Number, Keyboard.Numeric); + if(!string.IsNullOrWhiteSpace(_defaultCardNumber)) + { + CardNumberCell.Entry.Text = _defaultCardNumber; + } CardNameCell = new FormEntryCell(AppResources.CardholderName, nextElement: CardNumberCell.Entry); + if(!string.IsNullOrWhiteSpace(_defaultCardName)) + { + CardNameCell.Entry.Text = _defaultCardName; + } NameCell.NextElement = CardNameCell.Entry; // Build sections