From 707ff9b4e2686f915ee3d1198fcffdc6733424db Mon Sep 17 00:00:00 2001 From: Hixie Date: Thu, 4 Feb 2016 16:56:07 -0800 Subject: [PATCH] Implement hover touch exploration mode on Android. --- ...SemanticsToAndroidAccessibilityBridge.java | 50 +++++++++++++++++++ .../sky/shell/PlatformViewAndroid.java | 42 ++++++++++++---- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/sky/shell/platform/android/org/domokit/sky/shell/FlutterSemanticsToAndroidAccessibilityBridge.java b/sky/shell/platform/android/org/domokit/sky/shell/FlutterSemanticsToAndroidAccessibilityBridge.java index dfc2f3892..68b68445e 100644 --- a/sky/shell/platform/android/org/domokit/sky/shell/FlutterSemanticsToAndroidAccessibilityBridge.java +++ b/sky/shell/platform/android/org/domokit/sky/shell/FlutterSemanticsToAndroidAccessibilityBridge.java @@ -30,6 +30,7 @@ public class FlutterSemanticsToAndroidAccessibilityBridge extends AccessibilityN private PlatformViewAndroid mOwner; private SemanticsServer.Proxy mSemanticsServer; private PersistentAccessibilityNode mFocusedNode; + private PersistentAccessibilityNode mHoveredNode; FlutterSemanticsToAndroidAccessibilityBridge(PlatformViewAndroid owner, SemanticsServer.Proxy semanticsServer) { assert owner != null; @@ -177,6 +178,32 @@ public class FlutterSemanticsToAndroidAccessibilityBridge extends AccessibilityN return false; } + // TODO(ianh): implement findAccessibilityNodeInfosByText() + // TODO(ianh): implement findFocus() + + public void handleTouchExplorationExit() { + if (mHoveredNode != null) { + sendAccessibilityEvent(mHoveredNode.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); + mHoveredNode = null; + } + } + + public void handleTouchExploration(float x, float y) { + if (mTreeNodes.isEmpty()) + return; + assert mTreeNodes.containsKey(0); + PersistentAccessibilityNode newNode = mTreeNodes.get(0).hitTest(Math.round(x), Math.round(y)); + if (newNode != mHoveredNode) { + if (newNode != null) { + sendAccessibilityEvent(newNode.id, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); + } + if (mHoveredNode != null) { + sendAccessibilityEvent(mHoveredNode.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); + } + mHoveredNode = newNode; + } + } + @Override public void updateSemanticsTree(SemanticsNode[] nodes) { for (SemanticsNode node : nodes) { @@ -212,6 +239,12 @@ public class FlutterSemanticsToAndroidAccessibilityBridge extends AccessibilityN assert mTreeNodes.containsKey(node.id); assert mTreeNodes.get(node.id).parent == null; mTreeNodes.remove(node.id); + if (mFocusedNode == node) { + mFocusedNode = null; + } + if (mHoveredNode == node) { + mHoveredNode = null; + } for (PersistentAccessibilityNode child : node.children) { removePersistentNode(child); } @@ -219,7 +252,10 @@ public class FlutterSemanticsToAndroidAccessibilityBridge extends AccessibilityN public void reset(SemanticsServer.Proxy newSemanticsServer) { mTreeNodes.clear(); + mFocusedNode = null; + mHoveredNode = null; mSemanticsServer.close(); + sendAccessibilityEvent(0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); mSemanticsServer = newSemanticsServer; mSemanticsServer.addSemanticsListener(this); } @@ -375,6 +411,20 @@ public class FlutterSemanticsToAndroidAccessibilityBridge extends AccessibilityN } return globalRect; } + + public PersistentAccessibilityNode hitTest(int x, int y) { + Rect rect = getGlobalRect(); + if (!rect.contains(x, y)) + return null; + for (int index = children.size()-1; index >= 0; index -= 1) { + PersistentAccessibilityNode child = children.get(index); + PersistentAccessibilityNode result = child.hitTest(x, y); + if (result != null) { + return result; + } + } + return this; + } } @Override diff --git a/sky/shell/platform/android/org/domokit/sky/shell/PlatformViewAndroid.java b/sky/shell/platform/android/org/domokit/sky/shell/PlatformViewAndroid.java index 5694ce99d..878d6ceaa 100644 --- a/sky/shell/platform/android/org/domokit/sky/shell/PlatformViewAndroid.java +++ b/sky/shell/platform/android/org/domokit/sky/shell/PlatformViewAndroid.java @@ -248,6 +248,16 @@ public class PlatformViewAndroid extends SurfaceView return true; } + @Override + public boolean onHoverEvent(MotionEvent event) { + boolean handled = handleAccessibilityHoverEvent(event); + if (!handled) { + // TODO(ianh): Expose hover events to the platform, + // implementing ADD, REMOVE, etc. + } + return handled; + } + @Override protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { mMetrics.physicalWidth = width; @@ -332,10 +342,13 @@ public class PlatformViewAndroid extends SurfaceView // ACCESSIBILITY + private boolean mTouchExplorationEnabled = false; + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (mAccessibilityManager.isEnabled() || mAccessibilityManager.isTouchExplorationEnabled()) + mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled(); + if (mAccessibilityManager.isEnabled() || mTouchExplorationEnabled) ensureAccessibilityEnabled(); mAccessibilityManager.addAccessibilityStateChangeListener(this); mAccessibilityManager.addTouchExplorationStateChangeListener(this); @@ -349,9 +362,15 @@ public class PlatformViewAndroid extends SurfaceView @Override public void onTouchExplorationStateChanged(boolean enabled) { - if (enabled) + if (enabled) { + mTouchExplorationEnabled = true; ensureAccessibilityEnabled(); - // TODO(ianh): else, actually discard the state for exploration + } else { + mTouchExplorationEnabled = false; + if (mAccessibilityNodeProvider != null) { + mAccessibilityNodeProvider.handleTouchExplorationExit(); + } + } } @Override @@ -382,12 +401,15 @@ public class PlatformViewAndroid extends SurfaceView } } - // TODO(ianh): implement findAccessibilityNodeInfosByText() - - // TODO(ianh): implement findFocus() - - // TODO(ianh): implement touch exploration - - // TODO(ianh): implement accessibility focus + private boolean handleAccessibilityHoverEvent(MotionEvent event) { + if (!mTouchExplorationEnabled) + return false; + if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { + mAccessibilityNodeProvider.handleTouchExplorationExit(); + } else { + mAccessibilityNodeProvider.handleTouchExploration(event.getX(), event.getY()); + } + return true; + } }