1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-16 08:13:20 +00:00

Accessibility overlay support for username field and scroll tracking (#700)

* Trigger overlay prompt when focusing on username field

* Adjust accessibility overlay position in response to scroll events

* Get username EditText with a single pass of the node tree, plus additional cleanup
This commit is contained in:
Matt Portune
2020-01-13 17:14:57 -05:00
committed by Kyle Spearrin
parent eb16025800
commit d0ba4b6702
3 changed files with 165 additions and 61 deletions

View File

@@ -257,15 +257,48 @@ namespace Bit.Droid.Accessibility
IEnumerable<AccessibilityNodeInfo> passwordNodes)
{
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
var usernameEditText = GetUsernameEditText(allEditTexts);
var usernameEditText = GetUsernameEditTextIfPasswordExists(allEditTexts);
FillCredentials(usernameEditText, passwordNodes);
allEditTexts.Dispose();
usernameEditText = null;
}
public static AccessibilityNodeInfo GetUsernameEditText(IEnumerable<AccessibilityNodeInfo> allEditTexts)
public static AccessibilityNodeInfo GetUsernameEditTextIfPasswordExists(
IEnumerable<AccessibilityNodeInfo> allEditTexts)
{
return allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
AccessibilityNodeInfo previousEditText = null;
foreach(var editText in allEditTexts)
{
if(editText.Password)
{
return previousEditText;
}
previousEditText = editText;
}
return null;
}
public static bool IsUsernameEditText(AccessibilityNodeInfo root, AccessibilityEvent e)
{
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
var usernameEditText = GetUsernameEditTextIfPasswordExists(allEditTexts);
if(usernameEditText != null)
{
var isUsernameEditText = IsSameNode(usernameEditText, e.Source);
allEditTexts.Dispose();
usernameEditText = null;
return isUsernameEditText;
}
return false;
}
public static bool IsSameNode(AccessibilityNodeInfo info1, AccessibilityNodeInfo info2)
{
if(info1 != null && info2 != null)
{
return info1.Equals(info2) || info1.GetHashCode() == info2.GetHashCode();
}
return false;
}
public static bool OverlayPermitted()
@@ -294,20 +327,59 @@ namespace Bit.Droid.Accessibility
return view;
}
public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo root, AccessibilityEvent e)
public static WindowManagerLayoutParams GetOverlayLayoutParams()
{
WindowManagerTypes windowManagerType;
if(Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
windowManagerType = WindowManagerTypes.ApplicationOverlay;
}
else
{
windowManagerType = WindowManagerTypes.Phone;
}
var layoutParams = new WindowManagerLayoutParams(
ViewGroup.LayoutParams.WrapContent,
ViewGroup.LayoutParams.WrapContent,
windowManagerType,
WindowManagerFlags.NotFocusable | WindowManagerFlags.NotTouchModal,
Format.Transparent);
layoutParams.Gravity = GravityFlags.Bottom | GravityFlags.Left;
return layoutParams;
}
public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo root, AccessibilityNodeInfo anchorView)
{
var rootRect = new Rect();
root.GetBoundsInScreen(rootRect);
var rootRectHeight = rootRect.Height();
var eSrcRect = new Rect();
e.Source.GetBoundsInScreen(eSrcRect);
var eSrcRectLeft = eSrcRect.Left;
var eSrcRectTop = eSrcRect.Top;
var anchorViewRect = new Rect();
anchorView.GetBoundsInScreen(anchorViewRect);
var anchorViewRectLeft = anchorViewRect.Left;
var anchorViewRectTop = anchorViewRect.Top;
var navBarHeight = GetNavigationBarHeight();
var calculatedTop = rootRectHeight - eSrcRectTop - navBarHeight;
return new Point(eSrcRectLeft, calculatedTop);
var calculatedTop = rootRectHeight - anchorViewRectTop - navBarHeight;
return new Point(anchorViewRectLeft, calculatedTop);
}
public static Point GetOverlayAnchorPosition(int nodeHash, AccessibilityNodeInfo root, AccessibilityEvent e)
{
Point point = null;
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
foreach(var node in allEditTexts)
{
if(node.GetHashCode() == nodeHash)
{
point = GetOverlayAnchorPosition(root, node);
break;
}
}
allEditTexts.Dispose();
return point;
}
private static int GetStatusBarHeight()