using static Android.App.Assist.AssistStructure; using Android.App.Assist; using System.Collections.Generic; using Bit.Core; using Android.Content; using Bit.Core.Abstractions; using System.Threading.Tasks; using Android.OS; namespace Bit.Droid.Autofill { public class Parser { public static HashSet _excludedPackageIds = new HashSet { "android" }; private readonly AssistStructure _structure; private string _uri; private string _packageName; private string _website; public Parser(AssistStructure structure, Context applicationContext) { _structure = structure; ApplicationContext = applicationContext; } public Context ApplicationContext { get; set; } public FieldCollection FieldCollection { get; private set; } = new FieldCollection(); public string Uri { get { if (!string.IsNullOrWhiteSpace(_uri)) { return _uri; } var websiteNull = string.IsNullOrWhiteSpace(Website); if (websiteNull && string.IsNullOrWhiteSpace(PackageName)) { _uri = null; } else if (!websiteNull) { _uri = Website; } else { _uri = string.Concat(Core.Constants.AndroidAppProtocol, PackageName); } return _uri; } } public string PackageName { get => _packageName; set { if (string.IsNullOrWhiteSpace(value)) { _packageName = _uri = null; } _packageName = value; } } public string Website { get => _website; set { if (string.IsNullOrWhiteSpace(value)) { _website = _uri = null; } _website = value; } } public async Task ShouldAutofillAsync(IStateService stateService) { var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) && FieldCollection != null && FieldCollection.Fillable; if (fillable) { var blacklistedUris = await stateService.GetAutofillBlacklistedUrisAsync(); if (blacklistedUris != null && blacklistedUris.Count > 0) { fillable = !new HashSet(blacklistedUris).Contains(Uri); } } return fillable; } public void Parse() { string titlePackageId = null; for (var i = 0; i < _structure.WindowNodeCount; i++) { var node = _structure.GetWindowNodeAt(i); if (i == 0) { titlePackageId = GetTitlePackageId(node); } ParseNode(node.RootViewNode); } if (string.IsNullOrWhiteSpace(PackageName) && string.IsNullOrWhiteSpace(Website)) { PackageName = titlePackageId; } if (!AutofillHelpers.TrustedBrowsers.Contains(PackageName) && !AutofillHelpers.CompatBrowsers.Contains(PackageName)) { Website = null; } } private void ParseNode(ViewNode node) { SetPackageAndDomain(node); var hints = node.GetAutofillHints(); var isEditText = node.ClassName == "android.widget.EditText" || node?.HtmlInfo?.Tag == "input"; if (isEditText || (hints?.Length ?? 0) > 0) { FieldCollection.Add(new Field(node)); } else { FieldCollection.IgnoreAutofillIds.Add(node.AutofillId); } for (var i = 0; i < node.ChildCount; i++) { ParseNode(node.GetChildAt(i)); } } private void SetPackageAndDomain(ViewNode node) { if (string.IsNullOrWhiteSpace(PackageName) && !string.IsNullOrWhiteSpace(node.IdPackage) && !_excludedPackageIds.Contains(node.IdPackage)) { PackageName = node.IdPackage; } if (string.IsNullOrWhiteSpace(Website) && !string.IsNullOrWhiteSpace(node.WebDomain)) { var scheme = "http"; if ((int)Build.VERSION.SdkInt >= 28) { scheme = node.WebScheme; } Website = string.Format("{0}://{1}", scheme, node.WebDomain); } } private string GetTitlePackageId(WindowNode node) { if (node != null && !string.IsNullOrWhiteSpace(node.Title)) { var slashPosition = node.Title.IndexOf('/'); if (slashPosition > -1) { var packageId = node.Title.Substring(0, slashPosition); if (packageId.Contains(".")) { return packageId; } } } return null; } } }