243 lines
11 KiB
C#
243 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Android.App;
|
|
using Android.Content;
|
|
using Android.OS;
|
|
using Android.Runtime;
|
|
using Android.Service.Autofill;
|
|
using Android.Util;
|
|
using Android.Views;
|
|
using Android.Views.Autofill;
|
|
using Android.Widget;
|
|
using Android.Widget.Inline;
|
|
using AndroidX.AutoFill.Inline;
|
|
using AndroidX.AutoFill.Inline.V1;
|
|
using keepass2android;
|
|
using Kp2aAutofillParser;
|
|
|
|
namespace keepass2android.services.AutofillBase
|
|
{
|
|
/// <summary>
|
|
/// This is a class containing helper methods for building Autofill Datasets and Responses.
|
|
/// </summary>
|
|
public class AutofillHelper
|
|
{
|
|
|
|
public static InlinePresentation BuildInlinePresentation(InlinePresentationSpec inlinePresentationSpec,
|
|
string text, string subtext, int iconId, PendingIntent pendingIntent, Context context)
|
|
{
|
|
if ((int)Build.VERSION.SdkInt < 30 || inlinePresentationSpec == null)
|
|
{
|
|
return null;
|
|
}
|
|
//make sure we have a pendingIntent always not null
|
|
pendingIntent ??= PendingIntent.GetService(context, 0, new Intent(),
|
|
Util.AddMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, PendingIntentFlags.Mutable));
|
|
|
|
var slice = CreateInlinePresentationSlice(
|
|
inlinePresentationSpec,
|
|
text,
|
|
subtext,
|
|
iconId,
|
|
"Autofill option",
|
|
pendingIntent,
|
|
context);
|
|
if (slice != null)
|
|
{
|
|
return new InlinePresentation(slice, inlinePresentationSpec, false);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
private static Android.App.Slices.Slice CreateInlinePresentationSlice(
|
|
InlinePresentationSpec inlinePresentationSpec,
|
|
string text,
|
|
string subtext,
|
|
int iconId,
|
|
string contentDescription,
|
|
PendingIntent pendingIntent,
|
|
Context context)
|
|
{
|
|
var imeStyle = inlinePresentationSpec.Style;
|
|
|
|
if (!UiVersions.GetVersions(imeStyle).Contains(UiVersions.InlineUiVersion1))
|
|
{
|
|
return null;
|
|
}
|
|
var contentBuilder = InlineSuggestionUi.NewContentBuilder(pendingIntent)
|
|
.SetContentDescription(contentDescription);
|
|
if (!string.IsNullOrWhiteSpace(text))
|
|
{
|
|
contentBuilder.SetTitle(text);
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(subtext))
|
|
{
|
|
contentBuilder.SetSubtitle(subtext);
|
|
}
|
|
if (iconId > 0)
|
|
{
|
|
var icon = Android.Graphics.Drawables.Icon.CreateWithResource(context, iconId);
|
|
if (icon != null)
|
|
{
|
|
if (iconId == AppNames.LauncherIcon)
|
|
{
|
|
// Don't tint our logo
|
|
icon.SetTintBlendMode(Android.Graphics.BlendMode.Dst);
|
|
}
|
|
contentBuilder.SetStartIcon(icon);
|
|
}
|
|
}
|
|
return contentBuilder.Build().JavaCast<InlineSuggestionUi.Content>()?.Slice;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wraps autofill data in a LoginCredential Dataset object which can then be sent back to the
|
|
/// client View.
|
|
/// </summary>
|
|
public static Dataset NewDataset(Context context,
|
|
AutofillFieldMetadataCollection autofillFields,
|
|
FilledAutofillFieldCollection<ViewNodeInputField> filledAutofillFieldCollection,
|
|
IAutofillIntentBuilder intentBuilder,
|
|
Android.Widget.Inline.InlinePresentationSpec inlinePresentationSpec)
|
|
{
|
|
var datasetName = filledAutofillFieldCollection.DatasetName ?? "[noname]";
|
|
|
|
var datasetBuilder = new Dataset.Builder(NewRemoteViews(context.PackageName, datasetName, intentBuilder.AppIconResource));
|
|
datasetBuilder.SetId(datasetName);
|
|
|
|
var setValueAtLeastOnce = ApplyToFields(filledAutofillFieldCollection, autofillFields, datasetBuilder);
|
|
AddInlinePresentation(context, inlinePresentationSpec, datasetName, datasetBuilder, intentBuilder.AppIconResource, null);
|
|
|
|
if (setValueAtLeastOnce)
|
|
{
|
|
return datasetBuilder.Build();
|
|
}
|
|
/*else
|
|
{
|
|
Kp2aLog.Log("Failed to set at least one value. #fields=" + autofillFields.GetAutofillIds().Length + " " + autofillFields.FocusedAutofillCanonicalHints);
|
|
}*/
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates a Dataset.Builder with appropriate values for each AutofillId
|
|
/// in a AutofillFieldMetadataCollection.
|
|
///
|
|
/// In other words, it constructs an autofill Dataset.Builder
|
|
/// by applying saved values (from this FilledAutofillFieldCollection)
|
|
/// to Views specified in a AutofillFieldMetadataCollection, which represents the current
|
|
/// page the user is on.
|
|
/// </summary>
|
|
/// <returns><c>true</c>, if to fields was applyed, <c>false</c> otherwise.</returns>
|
|
/// <param name="filledAutofillFieldCollection"></param>
|
|
/// <param name="autofillFieldMetadataCollection">Autofill field metadata collection.</param>
|
|
/// <param name="datasetBuilder">Dataset builder.</param>
|
|
public static bool ApplyToFields(FilledAutofillFieldCollection<ViewNodeInputField> filledAutofillFieldCollection,
|
|
AutofillFieldMetadataCollection autofillFieldMetadataCollection, Dataset.Builder datasetBuilder)
|
|
{
|
|
bool setValueAtLeastOnce = false;
|
|
|
|
foreach (string hint in autofillFieldMetadataCollection.AllAutofillCanonicalHints)
|
|
{
|
|
foreach (AutofillFieldMetadata autofillFieldMetadata in autofillFieldMetadataCollection.GetFieldsForHint(hint))
|
|
{
|
|
FilledAutofillField filledAutofillField;
|
|
if (!filledAutofillFieldCollection.HintMap.TryGetValue(hint, out filledAutofillField) || (filledAutofillField == null))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var autofillId = autofillFieldMetadata.AutofillId;
|
|
var autofillType = autofillFieldMetadata.AutofillType;
|
|
switch (autofillType)
|
|
{
|
|
case AutofillType.List:
|
|
var listValue = autofillFieldMetadata.GetAutofillOptionIndex(filledAutofillField.TextValue);
|
|
if (listValue != -1)
|
|
{
|
|
datasetBuilder.SetValue(autofillId, AutofillValue.ForList(listValue));
|
|
setValueAtLeastOnce = true;
|
|
}
|
|
break;
|
|
case AutofillType.Date:
|
|
var dateValue = filledAutofillField.DateValue;
|
|
datasetBuilder.SetValue(autofillId, AutofillValue.ForDate((long)dateValue));
|
|
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;
|
|
default:
|
|
Log.Warn(CommonUtil.Tag, "Invalid autofill type - " + autofillType);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
if (!setValueAtLeastOnce)
|
|
{
|
|
Kp2aLog.Log("No value set. Hint keys : " + string.Join(",", HintMap.Keys));
|
|
foreach (string hint in autofillFieldMetadataCollection.AllAutofillCanonicalHints)
|
|
{
|
|
Kp2aLog.Log("No value set. Hint = " + hint);
|
|
foreach (AutofillFieldMetadata autofillFieldMetadata in autofillFieldMetadataCollection
|
|
.GetFieldsForHint(hint))
|
|
{
|
|
Kp2aLog.Log("No value set. fieldForHint = " + autofillFieldMetadata.AutofillId.ToString());
|
|
FilledAutofillField filledAutofillField;
|
|
if (!HintMap.TryGetValue(hint, out filledAutofillField) || (filledAutofillField == null))
|
|
{
|
|
Kp2aLog.Log("No value set. Hint map does not contain value, " +
|
|
(filledAutofillField == null));
|
|
continue;
|
|
}
|
|
|
|
Kp2aLog.Log("autofill type=" + autofillFieldMetadata.AutofillType);
|
|
}
|
|
}
|
|
}*/
|
|
|
|
return setValueAtLeastOnce;
|
|
}
|
|
|
|
public static void AddInlinePresentation(Context context, InlinePresentationSpec inlinePresentationSpec,
|
|
string datasetName, Dataset.Builder datasetBuilder, int iconId, PendingIntent pendingIntent)
|
|
{
|
|
if (inlinePresentationSpec != null)
|
|
{
|
|
var inlinePresentation = BuildInlinePresentation(inlinePresentationSpec, datasetName, "", iconId, pendingIntent, context);
|
|
if (inlinePresentation != null)
|
|
datasetBuilder.SetInlinePresentation(inlinePresentation);
|
|
}
|
|
}
|
|
|
|
public static RemoteViews NewRemoteViews(string packageName, string remoteViewsText,int drawableId)
|
|
{
|
|
RemoteViews presentation = new RemoteViews(packageName, Resource.Layout.autofill_service_list_item);
|
|
presentation.SetTextViewText(Resource.Id.text, remoteViewsText);
|
|
presentation.SetImageViewResource(Resource.Id.icon, drawableId);
|
|
return presentation;
|
|
}
|
|
|
|
internal static InlinePresentationSpec ExtractSpec(IList<InlinePresentationSpec> inlinePresentationSpecs, int index)
|
|
{
|
|
return inlinePresentationSpecs == null ? null : inlinePresentationSpecs[Math.Min(index, inlinePresentationSpecs.Count - 1)];
|
|
}
|
|
}
|
|
}
|