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 { /// /// This is a class containing helper methods for building Autofill Datasets and Responses. /// 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()?.Slice; } /// /// Wraps autofill data in a LoginCredential Dataset object which can then be sent back to the /// client View. /// public static Dataset NewDataset(Context context, AutofillFieldMetadataCollection autofillFields, FilledAutofillFieldCollection 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; } /// /// 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. /// /// true, if to fields was applyed, false otherwise. /// /// Autofill field metadata collection. /// Dataset builder. public static bool ApplyToFields(FilledAutofillFieldCollection 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 inlinePresentationSpecs, int index) { return inlinePresentationSpecs == null ? null : inlinePresentationSpecs[Math.Min(index, inlinePresentationSpecs.Count - 1)]; } } }