diff --git a/src/Android/Accessibility/AccessibilityHelpers.cs b/src/Android/Accessibility/AccessibilityHelpers.cs index 7bea9ebfd..62f4f593a 100644 --- a/src/Android/Accessibility/AccessibilityHelpers.cs +++ b/src/Android/Accessibility/AccessibilityHelpers.cs @@ -360,36 +360,31 @@ namespace Bit.Droid.Accessibility windowManagerType, WindowManagerFlags.NotFocusable | WindowManagerFlags.NotTouchModal, Format.Transparent); - layoutParams.Gravity = GravityFlags.Bottom | GravityFlags.Left; + layoutParams.Gravity = GravityFlags.Top | GravityFlags.Left; return layoutParams; } - public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo root, AccessibilityNodeInfo anchorView, - int rootRectHeight = 0) + public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo anchorView, int overlayViewHeight, + bool isOverlayAboveAnchor) { - if(rootRectHeight == 0) - { - rootRectHeight = GetNodeHeight(root); - } - var anchorViewRect = new Rect(); anchorView.GetBoundsInScreen(anchorViewRect); - var anchorViewRectLeft = anchorViewRect.Left; - var anchorViewRectTop = anchorViewRect.Top; + var anchorViewX = anchorViewRect.Left; + var anchorViewY = isOverlayAboveAnchor ? anchorViewRect.Top : anchorViewRect.Bottom; anchorViewRect.Dispose(); - - var calculatedTop = rootRectHeight - anchorViewRectTop; - if((int)Build.VERSION.SdkInt >= 24) + + if(isOverlayAboveAnchor) { - calculatedTop -= GetNavigationBarHeight(); + anchorViewY -= overlayViewHeight; } + anchorViewY -= GetStatusBarHeight(); - return new Point(anchorViewRectLeft, calculatedTop); + return new Point(anchorViewX, anchorViewY); } public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo anchorNode, AccessibilityNodeInfo root, - IEnumerable windows) + IEnumerable windows, int overlayViewHeight, bool isOverlayAboveAnchor) { Point point = null; if(anchorNode != null) @@ -407,32 +402,64 @@ namespace Bit.Droid.Accessibility // node.VisibleToUser doesn't always give us exactly what we want, so attempt to tighten up the range // of visibility - var rootNodeHeight = GetNodeHeight(root); - var limitLowY = 0; - var limitHighY = rootNodeHeight - GetNodeHeight(anchorNode); + var minY = 0; + int maxY; if(windows != null) { if(IsStatusBarExpanded(windows)) { return new Point(-1, -1); } - var inputWindowRect = GetInputMethodWindowRect(windows); - if(inputWindowRect != null) + maxY = GetApplicationVisibleHeight(windows); + } + else + { + var rootNodeHeight = GetNodeHeight(root); + if(rootNodeHeight == -1) { - limitLowY += inputWindowRect.Height(); - if(Build.VERSION.SdkInt >= BuildVersionCodes.Q) - { - limitLowY += GetNavigationBarHeight() + GetStatusBarHeight(); - } - inputWindowRect.Dispose(); + return null; } + maxY = rootNodeHeight - GetNavigationBarHeight(); } - point = GetOverlayAnchorPosition(root, anchorNode, rootNodeHeight); - if(point.Y < limitLowY || point.Y > limitHighY) + point = GetOverlayAnchorPosition(anchorNode, overlayViewHeight, isOverlayAboveAnchor); + if(point.Y < minY) { + if(isOverlayAboveAnchor) + { + // view nearing bounds, anchor to bottom + point.X = -1; + point.Y = 0; + } + else + { + // view out of bounds, hide overlay + point.X = -1; + point.Y = -1; + } + } + else if(point.Y > maxY) + { + if(isOverlayAboveAnchor) + { + // view out of bounds, hide overlay + point.X = -1; + point.Y = -1; + } + else + { + // view nearing bounds, anchor to top + point.X = 0; + point.Y = -1; + } + } + else if(isOverlayAboveAnchor && point.Y < (maxY - overlayViewHeight - GetNodeHeight(anchorNode))) + { + // This else block forces the overlay to return to bottom alignment as soon as space is available + // below the anchor view. Removing this will change the behavior to wait until there isn't enough + // space above the anchor view before returning to bottom alignment. point.X = -1; - point.Y = -1; + point.Y = 0; } } return point; @@ -456,28 +483,35 @@ namespace Bit.Droid.Accessibility return false; } - public static Rect GetInputMethodWindowRect(IEnumerable windows) + public static int GetApplicationVisibleHeight(IEnumerable windows) { - Rect windowRect = null; + var appWindowHeight = 0; + var nonAppWindowHeight = 0; if(windows != null) { foreach(var window in windows) { - if(window.Type == AccessibilityWindowType.InputMethod) + var windowRect = new Rect(); + window.GetBoundsInScreen(windowRect); + if(window.Type == AccessibilityWindowType.Application) { - windowRect = new Rect(); - window.GetBoundsInScreen(windowRect); - window.Recycle(); - break; + appWindowHeight += windowRect.Height(); + } + else + { + nonAppWindowHeight += windowRect.Height(); } - window.Recycle(); } } - return windowRect; + return appWindowHeight - nonAppWindowHeight; } public static int GetNodeHeight(AccessibilityNodeInfo node) { + if(node == null) + { + return -1; + } var nodeRect = new Rect(); node.GetBoundsInScreen(nodeRect); var nodeRectHeight = nodeRect.Height(); diff --git a/src/Android/Accessibility/AccessibilityService.cs b/src/Android/Accessibility/AccessibilityService.cs index d41b85d71..fec4e6490 100644 --- a/src/Android/Accessibility/AccessibilityService.cs +++ b/src/Android/Accessibility/AccessibilityService.cs @@ -31,9 +31,11 @@ namespace Bit.Droid.Accessibility private AccessibilityNodeInfo _anchorNode = null; private int _lastAnchorX = 0; private int _lastAnchorY = 0; + private bool _isOverlayAboveAnchor = false; private static bool _overlayAnchorObserverRunning = false; private IWindowManager _windowManager = null; private LinearLayout _overlayView = null; + private int _overlayViewHeight = 0; private long _lastAutoFillTime = 0; private Java.Lang.Runnable _overlayAnchorObserverRunnable = null; private Handler _handler = new Handler(Looper.MainLooper); @@ -180,6 +182,7 @@ namespace Bit.Droid.Accessibility _overlayView = null; _lastAnchorX = 0; _lastAnchorY = 0; + _isOverlayAboveAnchor = false; if(_anchorNode != null) { @@ -212,9 +215,24 @@ namespace Bit.Droid.Accessibility { return; } + + var intent = new Intent(this, typeof(AccessibilityActivity)); + intent.PutExtra("uri", uri); + intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop); + + _overlayView = AccessibilityHelpers.GetOverlayView(this); + _overlayView.Measure(View.MeasureSpec.MakeMeasureSpec(0, 0), + View.MeasureSpec.MakeMeasureSpec(0, 0)); + _overlayViewHeight = _overlayView.MeasuredHeight; + _overlayView.Click += (sender, eventArgs) => + { + CancelOverlayPrompt(); + StartActivity(intent); + }; var layoutParams = AccessibilityHelpers.GetOverlayLayoutParams(); - var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(root, e.Source); + var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(e.Source, _overlayViewHeight, + _isOverlayAboveAnchor); layoutParams.X = anchorPosition.X; layoutParams.Y = anchorPosition.Y; @@ -223,17 +241,6 @@ namespace Bit.Droid.Accessibility _windowManager = GetSystemService(WindowService).JavaCast(); } - var intent = new Intent(this, typeof(AccessibilityActivity)); - intent.PutExtra("uri", uri); - intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop); - - _overlayView = AccessibilityHelpers.GetOverlayView(this); - _overlayView.Click += (sender, eventArgs) => - { - CancelOverlayPrompt(); - StartActivity(intent); - }; - _anchorNode = e.Source; _lastAnchorX = anchorPosition.X; _lastAnchorY = anchorPosition.Y; @@ -281,7 +288,8 @@ namespace Bit.Droid.Accessibility windows = Windows; } - var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(_anchorNode, root, windows); + var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(_anchorNode, root, windows, + _overlayViewHeight, _isOverlayAboveAnchor); if(anchorPosition == null) { CancelOverlayPrompt(); @@ -292,9 +300,22 @@ namespace Bit.Droid.Accessibility if(_overlayView.Visibility != ViewStates.Gone) { _overlayView.Visibility = ViewStates.Gone; + System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Hidden"); } return; } + else if(anchorPosition.X == -1) + { + _isOverlayAboveAnchor = false; + System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Below Anchor"); + return; + } + else if(anchorPosition.Y == -1) + { + _isOverlayAboveAnchor = true; + System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Above Anchor"); + return; + } else if(anchorPosition.X == _lastAnchorX && anchorPosition.Y == _lastAnchorY) { if(_overlayView.Visibility != ViewStates.Visible)