implemented saving of data from autofill service (#9)
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Android.App.Assist;
|
||||
using Android.Service.Autofill;
|
||||
using Android.Views;
|
||||
@@ -65,47 +67,48 @@ namespace keepass2android.services.AutofillBase
|
||||
return -1;
|
||||
}
|
||||
|
||||
static readonly HashSet<string> _creditCardHints = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
View.AutofillHintCreditCardExpirationDate,
|
||||
View.AutofillHintCreditCardExpirationDay,
|
||||
View.AutofillHintCreditCardExpirationMonth,
|
||||
View.AutofillHintCreditCardExpirationYear,
|
||||
View.AutofillHintCreditCardNumber,
|
||||
View.AutofillHintCreditCardSecurityCode
|
||||
};
|
||||
|
||||
static readonly HashSet<string> _addressHints = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
View.AutofillHintPostalAddress,
|
||||
View.AutofillHintPostalCode
|
||||
};
|
||||
|
||||
void UpdateSaveTypeFromHints()
|
||||
{
|
||||
//TODO future add savetypes for W3cHints
|
||||
SaveType = 0;
|
||||
if (AutofillCanonicalHints == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var hint in AutofillCanonicalHints)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (AutofillCanonicalHints.Any(h => _creditCardHints.Contains(h)))
|
||||
{
|
||||
SaveType |= SaveDataType.CreditCard;
|
||||
}
|
||||
if (AutofillCanonicalHints.Any(h => h.Equals(View.AutofillHintEmailAddress, StringComparison.OrdinalIgnoreCase)))
|
||||
SaveType |= SaveDataType.EmailAddress;
|
||||
if (AutofillCanonicalHints.Any(h => _addressHints.Contains(h)))
|
||||
{
|
||||
SaveType |= SaveDataType.Address;
|
||||
}
|
||||
if (AutofillCanonicalHints.Any(h => h.Equals(View.AutofillHintUsername, StringComparison.OrdinalIgnoreCase)))
|
||||
SaveType |= SaveDataType.Username;
|
||||
|
||||
if (AutofillCanonicalHints.Any(h => h.Equals(View.AutofillHintPassword, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
SaveType |= SaveDataType.Password;
|
||||
SaveType &= ~SaveDataType.EmailAddress;
|
||||
SaveType &= ~SaveDataType.Username;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,8 @@ namespace keepass2android.services.AutofillBase
|
||||
responseBuilder.AddDataset(entryDataset);
|
||||
|
||||
AddQueryDataset(query, isManual, autofillIds, responseBuilder, !hasEntryDataset);
|
||||
responseBuilder.SetSaveInfo(new SaveInfo.Builder(parser.AutofillFields.SaveType,
|
||||
parser.AutofillFields.GetAutofillIds()).Build());
|
||||
|
||||
callback.OnSuccess(responseBuilder.Build());
|
||||
}
|
||||
@@ -122,10 +124,29 @@ namespace keepass2android.services.AutofillBase
|
||||
|
||||
public override void OnSaveRequest(SaveRequest request, SaveCallback callback)
|
||||
{
|
||||
//TODO implement save
|
||||
callback.OnFailure("Saving data is currently not implemented in Keepass2Android.");
|
||||
|
||||
var structure = request.FillContexts?.LastOrDefault()?.Structure;
|
||||
if (structure == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new StructureParser(this, structure);
|
||||
string query = parser.ParseForSave();
|
||||
try
|
||||
{
|
||||
HandleSaveRequest(parser, query);
|
||||
callback.OnSuccess();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
callback.OnFailure(e.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected abstract void HandleSaveRequest(StructureParser parser, string query);
|
||||
|
||||
|
||||
public override void OnConnected()
|
||||
{
|
||||
|
||||
@@ -6,6 +6,8 @@ using Android.Content;
|
||||
using Android.Text;
|
||||
using Android.Util;
|
||||
using Android.Views;
|
||||
using Android.Views.Autofill;
|
||||
using keepass2android.services.AutofillBase.model;
|
||||
using FilledAutofillFieldCollection = keepass2android.services.AutofillBase.model.FilledAutofillFieldCollection;
|
||||
|
||||
namespace keepass2android.services.AutofillBase
|
||||
@@ -61,32 +63,33 @@ namespace keepass2android.services.AutofillBase
|
||||
ParseLocked(forFill, isManualRequest, view, ref webDomain);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (AutofillFields.Empty)
|
||||
|
||||
List<AssistStructure.ViewNode> passwordFields = new List<AssistStructure.ViewNode>();
|
||||
List<AssistStructure.ViewNode> usernameFields = new List<AssistStructure.ViewNode>();
|
||||
if (AutofillFields.Empty)
|
||||
{
|
||||
var passwordFields = _editTextsWithoutHint
|
||||
.Where(IsPassword).ToList();
|
||||
passwordFields = _editTextsWithoutHint.Where(IsPassword).ToList();
|
||||
if (!passwordFields.Any())
|
||||
{
|
||||
passwordFields = _editTextsWithoutHint.Where(HasPasswordHint).ToList();
|
||||
}
|
||||
|
||||
foreach (var passwordField in passwordFields)
|
||||
{
|
||||
AutofillFields.Add(new AutofillFieldMetadata(passwordField, new[] { View.AutofillHintPassword }));
|
||||
var usernameField = _editTextsWithoutHint.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault();
|
||||
if (usernameField != null)
|
||||
{
|
||||
AutofillFields.Add(new AutofillFieldMetadata(usernameField, new[] {View.AutofillHintUsername}));
|
||||
usernameFields.Add(usernameField);
|
||||
}
|
||||
}
|
||||
//for some pages with two-step login, we don't see a password field and don't display the autofill for non-manual requests. But if the user forces autofill,
|
||||
//let's assume it is a username field:
|
||||
if (isManualRequest && !passwordFields.Any() && _editTextsWithoutHint.Count == 1)
|
||||
{
|
||||
AutofillFields.Add(new AutofillFieldMetadata(_editTextsWithoutHint.First(), new[] { View.AutofillHintUsername }));
|
||||
|
||||
usernameFields.Add(_editTextsWithoutHint.First());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -97,12 +100,30 @@ namespace keepass2android.services.AutofillBase
|
||||
{
|
||||
if (editText.IsFocused)
|
||||
{
|
||||
AutofillFields.Add(new AutofillFieldMetadata(editText, new[] { IsPassword(editText) || HasPasswordHint(editText) ? View.AutofillHintPassword : View.AutofillHintUsername }));
|
||||
if (IsPassword(editText) || HasPasswordHint(editText))
|
||||
passwordFields.Add(editText);
|
||||
else
|
||||
usernameFields.Add(editText);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (forFill)
|
||||
{
|
||||
foreach (var pf in passwordFields)
|
||||
AutofillFields.Add(new AutofillFieldMetadata(pf, new[] { View.AutofillHintPassword }));
|
||||
foreach (var uf in usernameFields)
|
||||
AutofillFields.Add(new AutofillFieldMetadata(uf, new[] { View.AutofillHintUsername }));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var pf in passwordFields)
|
||||
ClientFormData.Add(new FilledAutofillField(pf, new[] { View.AutofillHintPassword }));
|
||||
foreach (var uf in usernameFields)
|
||||
ClientFormData.Add(new FilledAutofillField(uf, new[] { View.AutofillHintUsername }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -182,10 +203,9 @@ namespace keepass2android.services.AutofillBase
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO implement save
|
||||
throw new NotImplementedException("TODO: Port and use AutoFill hints");
|
||||
//ClientFormData.Add(new FilledAutofillField(viewNode));
|
||||
}
|
||||
FilledAutofillField filledAutofillField = new FilledAutofillField(viewNode);
|
||||
ClientFormData.Add(filledAutofillField);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Android.App.Assist;
|
||||
using Android.Views.Autofill;
|
||||
using KeePassLib.Utility;
|
||||
|
||||
namespace keepass2android.services.AutofillBase.model
|
||||
{
|
||||
@@ -11,6 +12,17 @@ namespace keepass2android.services.AutofillBase.model
|
||||
public long? DateValue { get; set; }
|
||||
public bool? ToggleValue { get; set; }
|
||||
|
||||
public string ValueToString()
|
||||
{
|
||||
if (DateValue != null)
|
||||
{
|
||||
return TimeUtil.ConvertUnixTime((long)DateValue / 1000.0).ToLongDateString();
|
||||
}
|
||||
if (ToggleValue != null)
|
||||
return ToggleValue.ToString();
|
||||
return TextValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the autofill hints for the filled field. These are always lowercased for simpler string comparison.
|
||||
/// </summary>
|
||||
@@ -33,11 +45,17 @@ namespace keepass2android.services.AutofillBase.model
|
||||
|
||||
public FilledAutofillField()
|
||||
{}
|
||||
|
||||
public FilledAutofillField(AssistStructure.ViewNode viewNode)
|
||||
|
||||
public FilledAutofillField(AssistStructure.ViewNode viewNode)
|
||||
: this(viewNode, viewNode.GetAutofillHints())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public FilledAutofillField(AssistStructure.ViewNode viewNode, string[] hints)
|
||||
{
|
||||
|
||||
string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(viewNode.GetAutofillHints());
|
||||
string[] rawHints = AutofillHintsHelper.FilterForSupportedHints(hints);
|
||||
List<string> hintList = new List<string>();
|
||||
|
||||
string nextHint = null;
|
||||
@@ -81,9 +99,8 @@ namespace keepass2android.services.AutofillBase.model
|
||||
CommonUtil.loge($"Invalid hint: {rawHints[i]}");
|
||||
}
|
||||
}
|
||||
AutofillHints = hintList.ToArray();
|
||||
|
||||
//TODO port updated FilledAutofillField for saving
|
||||
AutofillHints = AutofillHintsHelper.ConvertToCanonicalHints(hintList.ToArray()).ToArray();
|
||||
|
||||
AutofillValue autofillValue = viewNode.AutofillValue;
|
||||
if (autofillValue != null)
|
||||
{
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace keepass2android.services.AutofillBase.model
|
||||
{
|
||||
if (AutofillHintsHelper.IsSupportedHint(hint))
|
||||
{
|
||||
HintMap.Add(hint, filledAutofillField);
|
||||
HintMap.TryAdd(hint, filledAutofillField);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -119,6 +119,14 @@ namespace keepass2android.services.Kp2aAutofill
|
||||
|
||||
private static readonly Dictionary<string, string> keyToHint = BuildKeyToHint();
|
||||
|
||||
public static string GetKp2aKeyFromHint(string canonicalHint)
|
||||
{
|
||||
var key = keyToHint.FirstOrDefault(p => p.Value.Equals(canonicalHint, StringComparison.OrdinalIgnoreCase)).Key;
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
return canonicalHint;
|
||||
return key;
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> BuildKeyToHint()
|
||||
{
|
||||
var result = new Dictionary<string, string>
|
||||
@@ -127,15 +135,15 @@ namespace keepass2android.services.Kp2aAutofill
|
||||
{PwDefs.PasswordField, View.AutofillHintPassword},
|
||||
{PwDefs.UrlField, W3cHints.URL},
|
||||
{
|
||||
Android.App.Application.Context.GetString(Resource.String.TemplateField_CreditCard_CVV),
|
||||
Application.Context.GetString(Resource.String.TemplateField_CreditCard_CVV),
|
||||
View.AutofillHintCreditCardSecurityCode
|
||||
},
|
||||
{
|
||||
Android.App.Application.Context.GetString(Resource.String.TemplateField_CreditCard_Owner),
|
||||
Application.Context.GetString(Resource.String.TemplateField_CreditCard_Owner),
|
||||
W3cHints.CC_NAME
|
||||
},
|
||||
{Android.App.Application.Context.GetString(Resource.String.TemplateField_Number), View.AutofillHintCreditCardNumber},
|
||||
{Android.App.Application.Context.GetString(Resource.String.TemplateField_IdCard_Name), View.AutofillHintName},
|
||||
{Application.Context.GetString(Resource.String.TemplateField_Number), View.AutofillHintCreditCardNumber},
|
||||
{Application.Context.GetString(Resource.String.TemplateField_IdCard_Name), View.AutofillHintName},
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Android;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
@@ -6,6 +7,10 @@ using Android.Runtime;
|
||||
using keepass2android.services.AutofillBase;
|
||||
using keepass2android.services.AutofillBase.model;
|
||||
using keepass2android.services.Kp2aAutofill;
|
||||
using Keepass2android.Pluginsdk;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Utility;
|
||||
using Org.Json;
|
||||
using AutofillServiceBase = keepass2android.services.AutofillBase.AutofillServiceBase;
|
||||
|
||||
namespace keepass2android.services
|
||||
@@ -34,6 +39,37 @@ namespace keepass2android.services
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override void HandleSaveRequest(StructureParser parser, string query)
|
||||
{
|
||||
|
||||
|
||||
var intent = new Intent(this, typeof(FileSelectActivity));
|
||||
|
||||
Dictionary<string, string> outputFields = new Dictionary<string, string>();
|
||||
foreach (var p in parser.ClientFormData.HintMap)
|
||||
{
|
||||
CommonUtil.logd(p.Key + " = " + p.Value.ValueToString());
|
||||
outputFields.TryAdd(ChooseForAutofillActivity.GetKp2aKeyFromHint(p.Key), p.Value.ValueToString());
|
||||
|
||||
}
|
||||
if (query != null)
|
||||
outputFields.TryAdd(PwDefs.UrlField, query);
|
||||
|
||||
JSONObject jsonOutput = new JSONObject(outputFields);
|
||||
var jsonOutputStr = jsonOutput.ToString();
|
||||
intent.PutExtra(Strings.ExtraEntryOutputData, jsonOutputStr);
|
||||
|
||||
JSONArray jsonProtectedFields = new JSONArray(
|
||||
(System.Collections.ICollection)new string[]{});
|
||||
intent.PutExtra(Strings.ExtraProtectedFieldsList, jsonProtectedFields.ToString());
|
||||
|
||||
intent.PutExtra(AppTask.AppTaskKey, "CreateEntryThenCloseTask");
|
||||
intent.PutExtra(CreateEntryThenCloseTask.ShowUserNotificationsKey, "false");
|
||||
|
||||
StartActivity(intent);
|
||||
|
||||
}
|
||||
|
||||
public override IAutofillIntentBuilder IntentBuilder => new Kp2aAutofillIntentBuilder();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user