diff --git a/src/Android/Accessibility/AccessibilityHelpers.cs b/src/Android/Accessibility/AccessibilityHelpers.cs index 39a2b218f..7a96673eb 100644 --- a/src/Android/Accessibility/AccessibilityHelpers.cs +++ b/src/Android/Accessibility/AccessibilityHelpers.cs @@ -124,6 +124,18 @@ namespace Bit.Droid.Accessibility "com.ss.squarehome2", "com.treydev.pns" }; + + // Be sure to keep these entries sorted alphabetically + public static Dictionary KnownUsernameFields => new List + { + new KnownUsernameField("accounts.google.com","ServiceLogin", "Email"), + new KnownUsernameField("amazon.com","signin", "ap_email_login"), + new KnownUsernameField("github.com", "", "user[login]-footer"), + new KnownUsernameField("paypal.com","signin", "email"), + new KnownUsernameField("signin.aws.amazon.com","signin", "resolving_input"), + new KnownUsernameField("signin.ebay.com","eBayISAPI.dll", "userid"), + + }.ToDictionary(n => n.UriAuthority); public static void PrintTestData(AccessibilityNodeInfo root, AccessibilityEvent e) { @@ -289,17 +301,50 @@ namespace Bit.Droid.Accessibility return nodes; } - public static void GetNodesAndFill(AccessibilityNodeInfo root, AccessibilityEvent e, - IEnumerable passwordNodes) + public static AccessibilityNodeInfo GetUsernameEditText(string uriString, + IEnumerable allEditTexts) { - var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false); - var usernameEditText = GetUsernameEditTextIfPasswordExists(allEditTexts); - FillCredentials(usernameEditText, passwordNodes); - allEditTexts.Dispose(); - usernameEditText = null; - } + string uriKey = null; + string uriLocalPath = null; + if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri)) + { + uriKey = uri.Authority; + uriLocalPath = uri.LocalPath; + } - public static AccessibilityNodeInfo GetUsernameEditTextIfPasswordExists( + if (!string.IsNullOrEmpty(uriKey)) + { + // Uncomment this to log values necessary for username field discovery + // foreach (var editText in allEditTexts) + // { + // System.Diagnostics.Debug.WriteLine(">>> uriKey: {0}, uriLocalPath: {1}, viewId: {2}", uriKey, + // uriLocalPath, editText.ViewIdResourceName); + // } + + if (KnownUsernameFields.ContainsKey(uriKey)) + { + var usernameField = KnownUsernameFields[uriKey]; + if (uriLocalPath.EndsWith(usernameField.UriPathEnd)) + { + foreach (var editText in allEditTexts) + { + foreach (var usernameViewId in usernameField.UsernameViewId.Split(",")) + { + if (usernameViewId == editText.ViewIdResourceName) + { + return editText; + } + } + } + } + } + } + + // no match found, attempt to establish username field based on password field + return GetUsernameEditTextIfPasswordExists(allEditTexts); + } + + private static AccessibilityNodeInfo GetUsernameEditTextIfPasswordExists( IEnumerable allEditTexts) { AccessibilityNodeInfo previousEditText = null; @@ -317,7 +362,8 @@ namespace Bit.Droid.Accessibility public static bool IsUsernameEditText(AccessibilityNodeInfo root, AccessibilityEvent e) { var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false); - var usernameEditText = GetUsernameEditTextIfPasswordExists(allEditTexts); + var uriString = GetUri(root); + var usernameEditText = GetUsernameEditText(uriString, allEditTexts); var isUsernameEditText = false; if (usernameEditText != null) diff --git a/src/Android/Accessibility/AccessibilityService.cs b/src/Android/Accessibility/AccessibilityService.cs index 69011e649..326653aff 100644 --- a/src/Android/Accessibility/AccessibilityService.cs +++ b/src/Android/Accessibility/AccessibilityService.cs @@ -168,22 +168,24 @@ namespace Bit.Droid.Accessibility public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e) { var filled = false; - var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, n => n.Password, false); - if (passwordNodes.Count > 0) + var uri = AccessibilityHelpers.GetUri(root); + if (uri != null && !uri.Contains(BitwardenWebsite) && + AccessibilityHelpers.NeedToAutofill(AccessibilityHelpers.LastCredentials, uri)) { - var uri = AccessibilityHelpers.GetUri(root); - if (uri != null && !uri.Contains(BitwardenWebsite)) + var allEditTexts = AccessibilityHelpers.GetWindowNodes(root, e, n => AccessibilityHelpers.EditText(n), false); + var usernameEditText = AccessibilityHelpers.GetUsernameEditText(uri, allEditTexts); + var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, n => n.Password, false); + if (usernameEditText != null || passwordNodes.Count > 0) { - if (AccessibilityHelpers.NeedToAutofill(AccessibilityHelpers.LastCredentials, uri)) - { - AccessibilityHelpers.GetNodesAndFill(root, e, passwordNodes); - filled = true; - _lastAutoFillTime = Java.Lang.JavaSystem.CurrentTimeMillis(); - } + AccessibilityHelpers.FillCredentials(usernameEditText, passwordNodes); + filled = true; + _lastAutoFillTime = Java.Lang.JavaSystem.CurrentTimeMillis(); + AccessibilityHelpers.LastCredentials = null; } - AccessibilityHelpers.LastCredentials = null; + allEditTexts.Dispose(); + passwordNodes.Dispose(); } - else if (AccessibilityHelpers.LastCredentials != null) + if (AccessibilityHelpers.LastCredentials != null) { Task.Run(async () => { @@ -191,7 +193,6 @@ namespace Bit.Droid.Accessibility AccessibilityHelpers.LastCredentials = null; }); } - passwordNodes.Dispose(); return filled; } diff --git a/src/Android/Accessibility/KnownUsernameField.cs b/src/Android/Accessibility/KnownUsernameField.cs new file mode 100644 index 000000000..2cd6a9193 --- /dev/null +++ b/src/Android/Accessibility/KnownUsernameField.cs @@ -0,0 +1,16 @@ +namespace Bit.Droid.Accessibility +{ + public class KnownUsernameField + { + public KnownUsernameField(string uriAuthority, string uriPathEnd, string usernameViewId) + { + UriAuthority = uriAuthority; + UriPathEnd = uriPathEnd; + UsernameViewId = usernameViewId; + } + + public string UriAuthority { get; set; } + public string UriPathEnd { get; set; } + public string UsernameViewId { get; set; } + } +} diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index 14c081e6b..b108aaf5f 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -100,6 +100,7 @@ +