diff options
| author | 2018-08-22 10:35:58 +0800 | |
|---|---|---|
| committer | 2018-11-02 11:28:42 +0800 | |
| commit | fa1b8dec6fa86ec35891b715058f2c714f1c30a6 (patch) | |
| tree | aa0b7ebcbe6562ed2f10334a3b1253928edfb8c2 | |
| parent | 8d79bdc341aba35a7f92321a2cf2d3d0c2d71be7 (diff) | |
Improve TouchDelegate Accessibility: Explore by Touch handle hover events
- Add TouchDelegate#onHoverEvent
Bug: 35702820
Test: Install TestBack with flag FLAG_REQUEST_TOUCH_EXPLORATION_MODE
enabled. In Settings APP, enable TestBack then touch/hover into
most left side of SwitchBar in the same subactivity to confirm
Switch delegated and hover enter event exist
Test: manually test with sample app in issue #7. Touch button delegate
and hover move over text view to confirm text get a11y focused.
Change-Id: I0abea81ea2fee4d391e2ee448710c5f0180f7533
| -rwxr-xr-x | api/current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/view/TouchDelegate.java | 61 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 22 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 10 |
4 files changed, 86 insertions, 8 deletions
diff --git a/api/current.txt b/api/current.txt index ead6157b58d7..3c6e8ffeb452 100755 --- a/api/current.txt +++ b/api/current.txt @@ -48355,6 +48355,7 @@ package android.view { ctor public TouchDelegate(android.graphics.Rect, android.view.View); method public android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo getTouchDelegateInfo(); method public boolean onTouchEvent(android.view.MotionEvent); + method public boolean onTouchExplorationHoverEvent(android.view.MotionEvent); field public static final int ABOVE = 1; // 0x1 field public static final int BELOW = 2; // 0x2 field public static final int TO_LEFT = 4; // 0x4 diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java index 6fb32e36fb3f..bef9f07afb5f 100644 --- a/core/java/android/view/TouchDelegate.java +++ b/core/java/android/view/TouchDelegate.java @@ -103,13 +103,13 @@ public class TouchDelegate { } /** - * Will forward touch events to the delegate view if the event is within the bounds + * Forward touch events to the delegate view if the event is within the bounds * specified in the constructor. * * @param event The touch event to forward - * @return True if the event was forwarded to the delegate, false otherwise. + * @return True if the event was consumed by the delegate, false otherwise. */ - public boolean onTouchEvent(MotionEvent event) { + public boolean onTouchEvent(@NonNull MotionEvent event) { int x = (int)event.getX(); int y = (int)event.getY(); boolean sendToDelegate = false; @@ -139,18 +139,65 @@ public class TouchDelegate { break; } if (sendToDelegate) { - final View delegateView = mDelegateView; - if (hit) { // Offset event coordinates to be inside the target view - event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2); + event.setLocation(mDelegateView.getWidth() / 2, mDelegateView.getHeight() / 2); } else { // Offset event coordinates to be outside the target view (in case it does // something like tracking pressed state) int slop = mSlop; event.setLocation(-(slop * 2), -(slop * 2)); } - handled = delegateView.dispatchTouchEvent(event); + handled = mDelegateView.dispatchTouchEvent(event); + } + return handled; + } + + /** + * Forward hover events to the delegate view if the event is within the bounds + * specified in the constructor and touch exploration is enabled. + * + * @param event The hover event to forward + * @return True if the event was consumed by the delegate, false otherwise. + * + * @see android.view.accessibility.AccessibilityManager#isTouchExplorationEnabled + */ + public boolean onTouchExplorationHoverEvent(@NonNull MotionEvent event) { + if (mBounds == null) { + return false; + } + + final int x = (int) event.getX(); + final int y = (int) event.getY(); + boolean hit = true; + boolean handled = false; + + final boolean isInbound = mBounds.contains(x, y); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_HOVER_ENTER: + mDelegateTargeted = isInbound; + break; + case MotionEvent.ACTION_HOVER_MOVE: + if (isInbound) { + mDelegateTargeted = true; + } else { + // delegated previously + if (mDelegateTargeted && !mSlopBounds.contains(x, y)) { + hit = false; + } + } + break; + case MotionEvent.ACTION_HOVER_EXIT: + mDelegateTargeted = true; + break; + } + if (mDelegateTargeted) { + if (hit) { + event.setLocation(mDelegateView.getWidth() / 2, mDelegateView.getHeight() / 2); + } else { + mDelegateTargeted = false; + } + handled = mDelegateView.dispatchHoverEvent(event); } return handled; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1493cd7f2eda..dffbbb7afb9e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -12830,6 +12830,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns true if the given point, in local coordinates, is inside the hovered child. + * + * @hide + */ + protected boolean pointInHoveredChild(MotionEvent event) { + return false; + } + + /** * Dispatch a generic motion event to the view under the first pointer. * <p> * Do not call this method directly. @@ -13584,6 +13593,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #onHoverChanged */ public boolean onHoverEvent(MotionEvent event) { + // Explore by touch should dispatch events to children under pointer first if any before + // dispatching to TouchDelegate. For children non-hoverable that will not consume events, + // it should also not delegate when they got the pointer hovered. + if (mTouchDelegate != null && !pointInHoveredChild(event)) { + final AccessibilityManager manager = AccessibilityManager.getInstance(mContext); + if (manager.isEnabled() && manager.isTouchExplorationEnabled() + && mTouchDelegate.onTouchExplorationHoverEvent(event)) { + return true; + } + } + // The root view may receive hover (or touch) events that are outside the bounds of // the window. This code ensures that we only send accessibility events for // hovers that are actually within the bounds of the root view. @@ -13598,7 +13618,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } else { if (action == MotionEvent.ACTION_HOVER_EXIT - || (action == MotionEvent.ACTION_MOVE + || (action == MotionEvent.ACTION_HOVER_MOVE && !pointInView(event.getX(), event.getY()))) { mSendingHoverAccessibilityEvents = false; sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 58febb050150..1e91aa87bfb7 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2383,6 +2383,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return mFirstHoverTarget != null; } + /** @hide */ + @Override + protected boolean pointInHoveredChild(MotionEvent event) { + if (mFirstHoverTarget != null) { + return isTransformedTouchPointInView(event.getX(), event.getY(), + mFirstHoverTarget.child, null); + } + return false; + } + @Override public void addChildrenForAccessibility(ArrayList<View> outChildren) { if (getAccessibilityNodeProvider() != null) { |