fix: Android hitSlop prop TalkBack selection#54199
Conversation
| if (ev.isFromSource(android.view.InputDevice.SOURCE_CLASS_POINTER) && | ||
| (ev.action == MotionEvent.ACTION_HOVER_ENTER || ev.action == MotionEvent.ACTION_HOVER_MOVE)) { |
There was a problem hiding this comment.
Hover actions are not only used by TalkBack, but also by many other platforms (eg VR).
Is there a way to scope this changes more, or ideally, reuse logic from TouchTargetHelper?
There was a problem hiding this comment.
Oh sure that makes sense. I will take another look and see what I can find.
There was a problem hiding this comment.
I think the simplest way to do this here was just to check if touch exploration is enabled and keep the rest of the changes, like this: 3550da1.
I looked at TouchTargetHelper and I see there's methods to do hitSlop checking, which we could reuse, but I think we'd have to make them public? If that works, or you had something else in mind, let me know and I'll keep iterating.
javache
left a comment
There was a problem hiding this comment.
Let's make the change to expose the API - once we have code thats working and not duplicating logic, we can figure out how to reduce visibility changes.
We shouldn't be getting the accessibilitymanager or calling isTouchExplorationEnabled per frame though, as that will cause us to drop frames.
| val accessibilityManager = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager | ||
| if (accessibilityManager?.isTouchExplorationEnabled == true && |
There was a problem hiding this comment.
This is still pretty expensive to do on a per-input basis (I think it needs to a Binder call)
There was a problem hiding this comment.
Ah yeah that makes sense. I've added some internal state to ReactViewGroup and a TouchExplorationStateChangeListener to manage it.
fix: clean up unncessary bounds checking fix: use listener instead
|
@javache - thanks again for your time reviewing. I exposed the |
| private var accessibilityStateChangeListener: | ||
| AccessibilityManager.AccessibilityStateChangeListener? = | ||
| null | ||
| private var touchExplorationStateChangeListener: | ||
| AccessibilityManager.TouchExplorationStateChangeListener? = | ||
| null |
There was a problem hiding this comment.
Both of these should probably live in ReactViewManager or similar - it's wasteful to have every view observe this independently.
There was a problem hiding this comment.
Hey @javache - I'd be happy to make this change, but can you provide any more specifics? I just want to respect your time and make sure I understand what you're looking for more clearly before asking for another review.
There was a problem hiding this comment.
Hey @javache - any additional thoughts? I'd love to move this forward if possible, would love more details on your feedback. Thanks in advance!
popsiclelmlm
left a comment
There was a problem hiding this comment.
I noticed one lifecycle issue in the current version:
onAttachedToWindow() only registers the TouchExplorationStateChangeListener when touchExplorationStateChangeListener == null, but onDetachedFromWindow() removes the listener without clearing that field. If the same ReactViewGroup instance is detached and later reattached, it will skip re-registering the listener because the field is still non-null, leaving isTouchExplorationEnabled stale after reattach.
A small fix would be to set touchExplorationStateChangeListener = null after removal, or to separate “listener object exists” from “listener is currently registered”. A focused lifecycle test for detach/reattach would be helpful too, since this PR relies on the cached touch-exploration state staying current.
|
Thanks, @popsiclelmlm. I can push up a commit soon, although I think my fork is having some problems (or maybe GitHub is?). I'll try again sometime this week. Should be a small fix. |
Summary:
Fixes #54187 by intercepting TalkBack hover events inside
dispatchGenericMotionEventand checking if the tap falls inside the child's hitSlop area. If the tap occurs in the hitSlop zone, the method now requests accessibility focus on that child view.Changelog:
Pick one each for the category and type tags:
[ANDROID] [FIXED] - fix hitSlop prop not providing full hitSlop boundaries to TalkBack
Test Plan:
You can test this in the "Pressable Hit Slop" segment of the
rn-testerapp on Android.yarn installyarn androidScreen recording of fixed behavior in this branch
fixed.mov
Screen recording of broken behavior on
mainbroken.mov