mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 02:18:27 +03:00
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:
parent
93132f5d7b
commit
bbd8615cda
2 changed files with 107 additions and 52 deletions
|
@ -360,36 +360,31 @@ namespace Bit.Droid.Accessibility
|
||||||
windowManagerType,
|
windowManagerType,
|
||||||
WindowManagerFlags.NotFocusable | WindowManagerFlags.NotTouchModal,
|
WindowManagerFlags.NotFocusable | WindowManagerFlags.NotTouchModal,
|
||||||
Format.Transparent);
|
Format.Transparent);
|
||||||
layoutParams.Gravity = GravityFlags.Bottom | GravityFlags.Left;
|
layoutParams.Gravity = GravityFlags.Top | GravityFlags.Left;
|
||||||
|
|
||||||
return layoutParams;
|
return layoutParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo root, AccessibilityNodeInfo anchorView,
|
public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo anchorView, int overlayViewHeight,
|
||||||
int rootRectHeight = 0)
|
bool isOverlayAboveAnchor)
|
||||||
{
|
{
|
||||||
if(rootRectHeight == 0)
|
|
||||||
{
|
|
||||||
rootRectHeight = GetNodeHeight(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
var anchorViewRect = new Rect();
|
var anchorViewRect = new Rect();
|
||||||
anchorView.GetBoundsInScreen(anchorViewRect);
|
anchorView.GetBoundsInScreen(anchorViewRect);
|
||||||
var anchorViewRectLeft = anchorViewRect.Left;
|
var anchorViewX = anchorViewRect.Left;
|
||||||
var anchorViewRectTop = anchorViewRect.Top;
|
var anchorViewY = isOverlayAboveAnchor ? anchorViewRect.Top : anchorViewRect.Bottom;
|
||||||
anchorViewRect.Dispose();
|
anchorViewRect.Dispose();
|
||||||
|
|
||||||
var calculatedTop = rootRectHeight - anchorViewRectTop;
|
if(isOverlayAboveAnchor)
|
||||||
if((int)Build.VERSION.SdkInt >= 24)
|
|
||||||
{
|
{
|
||||||
calculatedTop -= GetNavigationBarHeight();
|
anchorViewY -= overlayViewHeight;
|
||||||
}
|
}
|
||||||
|
anchorViewY -= GetStatusBarHeight();
|
||||||
|
|
||||||
return new Point(anchorViewRectLeft, calculatedTop);
|
return new Point(anchorViewX, anchorViewY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo anchorNode, AccessibilityNodeInfo root,
|
public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo anchorNode, AccessibilityNodeInfo root,
|
||||||
IEnumerable<AccessibilityWindowInfo> windows)
|
IEnumerable<AccessibilityWindowInfo> windows, int overlayViewHeight, bool isOverlayAboveAnchor)
|
||||||
{
|
{
|
||||||
Point point = null;
|
Point point = null;
|
||||||
if(anchorNode != 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
|
// node.VisibleToUser doesn't always give us exactly what we want, so attempt to tighten up the range
|
||||||
// of visibility
|
// of visibility
|
||||||
var rootNodeHeight = GetNodeHeight(root);
|
var minY = 0;
|
||||||
var limitLowY = 0;
|
int maxY;
|
||||||
var limitHighY = rootNodeHeight - GetNodeHeight(anchorNode);
|
|
||||||
if(windows != null)
|
if(windows != null)
|
||||||
{
|
{
|
||||||
if(IsStatusBarExpanded(windows))
|
if(IsStatusBarExpanded(windows))
|
||||||
{
|
{
|
||||||
return new Point(-1, -1);
|
return new Point(-1, -1);
|
||||||
}
|
}
|
||||||
var inputWindowRect = GetInputMethodWindowRect(windows);
|
maxY = GetApplicationVisibleHeight(windows);
|
||||||
if(inputWindowRect != null)
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var rootNodeHeight = GetNodeHeight(root);
|
||||||
|
if(rootNodeHeight == -1)
|
||||||
{
|
{
|
||||||
limitLowY += inputWindowRect.Height();
|
return null;
|
||||||
if(Build.VERSION.SdkInt >= BuildVersionCodes.Q)
|
|
||||||
{
|
|
||||||
limitLowY += GetNavigationBarHeight() + GetStatusBarHeight();
|
|
||||||
}
|
|
||||||
inputWindowRect.Dispose();
|
|
||||||
}
|
}
|
||||||
|
maxY = rootNodeHeight - GetNavigationBarHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
point = GetOverlayAnchorPosition(root, anchorNode, rootNodeHeight);
|
point = GetOverlayAnchorPosition(anchorNode, overlayViewHeight, isOverlayAboveAnchor);
|
||||||
if(point.Y < limitLowY || point.Y > limitHighY)
|
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.X = -1;
|
||||||
point.Y = -1;
|
point.Y = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return point;
|
return point;
|
||||||
|
@ -456,28 +483,35 @@ namespace Bit.Droid.Accessibility
|
||||||
return false;
|
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)
|
if(windows != null)
|
||||||
{
|
{
|
||||||
foreach(var window in windows)
|
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();
|
appWindowHeight += windowRect.Height();
|
||||||
window.GetBoundsInScreen(windowRect);
|
}
|
||||||
window.Recycle();
|
else
|
||||||
break;
|
{
|
||||||
|
nonAppWindowHeight += windowRect.Height();
|
||||||
}
|
}
|
||||||
window.Recycle();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return windowRect;
|
return appWindowHeight - nonAppWindowHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetNodeHeight(AccessibilityNodeInfo node)
|
public static int GetNodeHeight(AccessibilityNodeInfo node)
|
||||||
{
|
{
|
||||||
|
if(node == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
var nodeRect = new Rect();
|
var nodeRect = new Rect();
|
||||||
node.GetBoundsInScreen(nodeRect);
|
node.GetBoundsInScreen(nodeRect);
|
||||||
var nodeRectHeight = nodeRect.Height();
|
var nodeRectHeight = nodeRect.Height();
|
||||||
|
|
|
@ -31,9 +31,11 @@ namespace Bit.Droid.Accessibility
|
||||||
private AccessibilityNodeInfo _anchorNode = null;
|
private AccessibilityNodeInfo _anchorNode = null;
|
||||||
private int _lastAnchorX = 0;
|
private int _lastAnchorX = 0;
|
||||||
private int _lastAnchorY = 0;
|
private int _lastAnchorY = 0;
|
||||||
|
private bool _isOverlayAboveAnchor = false;
|
||||||
private static bool _overlayAnchorObserverRunning = false;
|
private static bool _overlayAnchorObserverRunning = false;
|
||||||
private IWindowManager _windowManager = null;
|
private IWindowManager _windowManager = null;
|
||||||
private LinearLayout _overlayView = null;
|
private LinearLayout _overlayView = null;
|
||||||
|
private int _overlayViewHeight = 0;
|
||||||
private long _lastAutoFillTime = 0;
|
private long _lastAutoFillTime = 0;
|
||||||
private Java.Lang.Runnable _overlayAnchorObserverRunnable = null;
|
private Java.Lang.Runnable _overlayAnchorObserverRunnable = null;
|
||||||
private Handler _handler = new Handler(Looper.MainLooper);
|
private Handler _handler = new Handler(Looper.MainLooper);
|
||||||
|
@ -180,6 +182,7 @@ namespace Bit.Droid.Accessibility
|
||||||
_overlayView = null;
|
_overlayView = null;
|
||||||
_lastAnchorX = 0;
|
_lastAnchorX = 0;
|
||||||
_lastAnchorY = 0;
|
_lastAnchorY = 0;
|
||||||
|
_isOverlayAboveAnchor = false;
|
||||||
|
|
||||||
if(_anchorNode != null)
|
if(_anchorNode != null)
|
||||||
{
|
{
|
||||||
|
@ -212,9 +215,24 @@ namespace Bit.Droid.Accessibility
|
||||||
{
|
{
|
||||||
return;
|
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 layoutParams = AccessibilityHelpers.GetOverlayLayoutParams();
|
||||||
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(root, e.Source);
|
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(e.Source, _overlayViewHeight,
|
||||||
|
_isOverlayAboveAnchor);
|
||||||
layoutParams.X = anchorPosition.X;
|
layoutParams.X = anchorPosition.X;
|
||||||
layoutParams.Y = anchorPosition.Y;
|
layoutParams.Y = anchorPosition.Y;
|
||||||
|
|
||||||
|
@ -223,17 +241,6 @@ namespace Bit.Droid.Accessibility
|
||||||
_windowManager = GetSystemService(WindowService).JavaCast<IWindowManager>();
|
_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;
|
_anchorNode = e.Source;
|
||||||
_lastAnchorX = anchorPosition.X;
|
_lastAnchorX = anchorPosition.X;
|
||||||
_lastAnchorY = anchorPosition.Y;
|
_lastAnchorY = anchorPosition.Y;
|
||||||
|
@ -281,7 +288,8 @@ namespace Bit.Droid.Accessibility
|
||||||
windows = Windows;
|
windows = Windows;
|
||||||
}
|
}
|
||||||
|
|
||||||
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(_anchorNode, root, windows);
|
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(_anchorNode, root, windows,
|
||||||
|
_overlayViewHeight, _isOverlayAboveAnchor);
|
||||||
if(anchorPosition == null)
|
if(anchorPosition == null)
|
||||||
{
|
{
|
||||||
CancelOverlayPrompt();
|
CancelOverlayPrompt();
|
||||||
|
@ -292,9 +300,22 @@ namespace Bit.Droid.Accessibility
|
||||||
if(_overlayView.Visibility != ViewStates.Gone)
|
if(_overlayView.Visibility != ViewStates.Gone)
|
||||||
{
|
{
|
||||||
_overlayView.Visibility = ViewStates.Gone;
|
_overlayView.Visibility = ViewStates.Gone;
|
||||||
|
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Hidden");
|
||||||
}
|
}
|
||||||
return;
|
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)
|
else if(anchorPosition.X == _lastAnchorX && anchorPosition.Y == _lastAnchorY)
|
||||||
{
|
{
|
||||||
if(_overlayView.Visibility != ViewStates.Visible)
|
if(_overlayView.Visibility != ViewStates.Visible)
|
||||||
|
|
Loading…
Reference in a new issue