Align overlay to bottom or top of anchor view depending on available space (bottom by default on initial focus). Establishing visible app height now works much better on Android 5.0+. (#718)

This commit is contained in:
Matt Portune 2020-02-05 19:40:44 -05:00 committed by GitHub
parent 93132f5d7b
commit bbd8615cda
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 107 additions and 52 deletions

View file

@ -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<AccessibilityWindowInfo> windows)
IEnumerable<AccessibilityWindowInfo> 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<AccessibilityWindowInfo> windows)
public static int GetApplicationVisibleHeight(IEnumerable<AccessibilityWindowInfo> 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();

View file

@ -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<IWindowManager>();
}
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)