* adding a lock to avoid flickering/disappearing prompt
 * returning a fill-response instead of a dataset from authentication activity
 * immediately present one search result (if there is a match) as a dataset item in the autofill prompt
This commit is contained in:
Philipp Crocoll
2021-01-02 12:31:19 +01:00
parent 42eb8222fc
commit ca61fa9d1d
5 changed files with 147 additions and 101 deletions

View File

@@ -28,6 +28,7 @@ namespace keepass2android.services.AutofillBase
if (datasetName != null)
{
var datasetBuilder = new Dataset.Builder(NewRemoteViews(context.PackageName, datasetName, intentBuilder.AppIconResource));
datasetBuilder.SetId(datasetName);
var setValueAtLeastOnce = filledAutofillFieldCollection.ApplyToFields(autofillFields, datasetBuilder);
if (setValueAtLeastOnce)

View File

@@ -10,6 +10,7 @@ using Android.Service.Autofill;
using Android.Util;
using Android.Views.Autofill;
using Android.Widget;
using Java.Util.Concurrent.Atomic;
using keepass2android.services.AutofillBase.model;
namespace keepass2android.services.AutofillBase
@@ -30,6 +31,10 @@ namespace keepass2android.services.AutofillBase
public abstract class AutofillServiceBase: AutofillService
{
//use a lock to avoid returning a response several times in buggy Firefox during one connection: this avoids flickering
//and disappearing of the autofill prompt.
private AtomicBoolean _lock = new AtomicBoolean();
public AutofillServiceBase()
{
@@ -83,15 +88,20 @@ namespace keepass2android.services.AutofillBase
{
bool isManual = (request.Flags & FillRequest.FlagManualRequest) != 0;
CommonUtil.logd( "onFillRequest " + (isManual ? "manual" : "auto"));
var structure = request.FillContexts[request.FillContexts.Count - 1].Structure;
var structure = request.FillContexts.Last().Structure;
if (!_lock.Get())
{
_lock.Set(true);
//TODO support package signature verification as soon as this is supported in Keepass storage
var clientState = request.ClientState;
CommonUtil.logd( "onFillRequest(): data=" + CommonUtil.BundleToString(clientState));
CommonUtil.logd("onFillRequest(): data=" + CommonUtil.BundleToString(clientState));
cancellationSignal.CancelEvent += (sender, e) => {
cancellationSignal.CancelEvent += (sender, e) =>
{
Log.Warn(CommonUtil.Tag, "Cancel autofill not implemented yet.");
};
// Parse AutoFill data in Activity
@@ -117,31 +127,36 @@ namespace keepass2android.services.AutofillBase
{
var responseBuilder = new FillResponse.Builder();
Dataset entryDataset = null;
bool hasEntryDataset = false;
if (query.IncompatiblePackageAndDomain == false)
{
//domain and package are compatible. Use Domain if available and package otherwise. Can fill without warning.
entryDataset = BuildEntryDataset(query.DomainOrPackage, query.WebDomain, query.PackageName, autofillIds, parser, DisplayWarning.None);
}
else
foreach (var entryDataset in BuildEntryDatasets(query.DomainOrPackage, query.WebDomain,
query.PackageName,
autofillIds, parser, DisplayWarning.None)
)
{
//domain or package are incompatible. Don't show the entry. (Tried to do so first but behavior was not consistent)
//entryDataset = BuildEntryDataset(query.WebDomain, query.WebDomain, query.PackageName, autofillIds, parser, DisplayWarning.FillDomainInUntrustedApp);
}
bool hasEntryDataset = entryDataset != null;
if (entryDataset != null)
responseBuilder.AddDataset(entryDataset);
hasEntryDataset = true;
}
}
{
if (query.WebDomain != null)
AddQueryDataset(query.WebDomain,
query.WebDomain, query.PackageName,
isManual, autofillIds, responseBuilder, !hasEntryDataset, query.IncompatiblePackageAndDomain ? DisplayWarning.FillDomainInUntrustedApp : DisplayWarning.None);
isManual, autofillIds, responseBuilder, !hasEntryDataset,
query.IncompatiblePackageAndDomain
? DisplayWarning.FillDomainInUntrustedApp
: DisplayWarning.None);
else
AddQueryDataset(query.PackageNameWithPseudoSchema,
query.WebDomain, query.PackageName,
isManual, autofillIds, responseBuilder, !hasEntryDataset, DisplayWarning.None);
}
AddDisableDataset(query.DomainOrPackage, autofillIds, responseBuilder, isManual);
@@ -163,31 +178,41 @@ namespace keepass2android.services.AutofillBase
callback.OnSuccess(null);
}
}
}
private Dataset BuildEntryDataset(string query, string queryDomain, string queryPackage, AutofillId[] autofillIds, StructureParser parser,
private List<Dataset> BuildEntryDatasets(string query, string queryDomain, string queryPackage, AutofillId[] autofillIds, StructureParser parser,
DisplayWarning warning)
{
var filledAutofillFieldCollection = GetSuggestedEntry(query);
List<Dataset> result = new List<Dataset>();
var suggestedEntries = GetSuggestedEntries(query).ToDictionary(e => e.DatasetName, e => e);
foreach (var filledAutofillFieldCollection in suggestedEntries.Values)
{
if (filledAutofillFieldCollection == null)
return null;
continue;
if (warning == DisplayWarning.None)
{
//can return an actual dataset
int partitionIndex = AutofillHintsHelper.GetPartitionIndex(parser.AutofillFields.FocusedAutofillCanonicalHints.FirstOrDefault());
FilledAutofillFieldCollection partitionData = AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, partitionIndex);
int partitionIndex =
AutofillHintsHelper.GetPartitionIndex(parser.AutofillFields.FocusedAutofillCanonicalHints
.FirstOrDefault());
FilledAutofillFieldCollection partitionData =
AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, partitionIndex);
return AutofillHelper.NewDataset(this, parser.AutofillFields, partitionData, IntentBuilder);
result.Add(AutofillHelper.NewDataset(this, parser.AutofillFields, partitionData, IntentBuilder));
}
else
{
//return an "auth" dataset (actually for just warning the user in case domain/package dont match)
var sender = IntentBuilder.GetAuthIntentSenderForWarning(this, query, queryDomain, queryPackage, warning);
var sender =
IntentBuilder.GetAuthIntentSenderForWarning(this, query, queryDomain, queryPackage, warning);
var datasetName = filledAutofillFieldCollection.DatasetName;
if (datasetName == null)
return null;
RemoteViews presentation = AutofillHelper.NewRemoteViews(PackageName, datasetName, AppNames.LauncherIcon);
RemoteViews presentation =
AutofillHelper.NewRemoteViews(PackageName, datasetName, AppNames.LauncherIcon);
var datasetBuilder = new Dataset.Builder(presentation);
datasetBuilder.SetAuthentication(sender);
@@ -197,13 +222,16 @@ namespace keepass2android.services.AutofillBase
datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER"));
}
return datasetBuilder.Build();
result.Add(datasetBuilder.Build());
}
}
return result;
}
protected abstract FilledAutofillFieldCollection GetSuggestedEntry(string query);
protected abstract List<FilledAutofillFieldCollection> GetSuggestedEntries(string query);
public enum DisplayWarning
{
@@ -335,6 +363,8 @@ namespace keepass2android.services.AutofillBase
public override void OnDisconnected()
{
_lock.Set(false);
CommonUtil.logd( "onDisconnected");
}

View File

@@ -182,6 +182,7 @@ namespace keepass2android.services.AutofillBase
FilledAutofillFieldCollection partitionData = AutofillHintsHelper.FilterForPartition(clientFormDataMap, partitionIndex);
ReplyIntent = new Intent();
SetDatasetIntent(AutofillHelper.NewDataset(this, autofillFields, partitionData, IntentBuilder));
SetResult(Result.Ok, ReplyIntent);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
@@ -229,7 +230,9 @@ namespace keepass2android.services.AutofillBase
protected void SetDatasetIntent(Dataset dataset)
{
ReplyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
var responseBuilder = new FillResponse.Builder();
responseBuilder.AddDataset(dataset);
ReplyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, responseBuilder.Build());
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Android;
using Android.App;
using Android.Content;
@@ -9,6 +10,7 @@ using keepass2android.services.AutofillBase.model;
using keepass2android.services.Kp2aAutofill;
using Keepass2android.Pluginsdk;
using KeePassLib;
using KeePassLib.Collections;
using KeePassLib.Utility;
using Org.Json;
using AutofillServiceBase = keepass2android.services.AutofillBase.AutofillServiceBase;
@@ -31,12 +33,22 @@ namespace keepass2android.services
{
}
protected override FilledAutofillFieldCollection GetSuggestedEntry(string query)
protected override List<FilledAutofillFieldCollection> GetSuggestedEntries(string query)
{
if (App.Kp2a.LastOpenedEntry?.SearchUrl == query)
return ChooseForAutofillActivity.GetFilledAutofillFieldCollectionFromEntry(
App.Kp2a.LastOpenedEntry, this);
return null;
var foundEntries = (ShareUrlResults.GetSearchResultsForUrl(query)?.Entries ?? new PwObjectList<PwEntry>())
.Select(e => new PwEntryOutput(e, App.Kp2a.FindDatabaseForElement(e)))
.ToList();
if ((App.Kp2a.LastOpenedEntry?.SearchUrl == query) && !foundEntries.Any(e => e.Uuid.Equals(App.Kp2a.LastOpenedEntry?.Uuid)))
{
foundEntries.Clear();
foundEntries.Add(App.Kp2a.LastOpenedEntry);
}
//it seems like at least with Firefox we can have at most 3 datasets. Reserve space for the disable/enable dataset and the "fill with KP2A" which allows to select another item
//so take only 1:
return foundEntries.Take(1).Select(e => ChooseForAutofillActivity.GetFilledAutofillFieldCollectionFromEntry(e, this))
.ToList();
}
protected override void HandleSaveRequest(StructureParser parser, StructureParser.AutofillTargetId query)