From 650ba28fd0f15bbeae5d5025cbe8fc2ff500e0e6 Mon Sep 17 00:00:00 2001 From: Hugo Holgersson Date: Thu, 19 Sep 2019 14:37:26 +0200 Subject: [PATCH] T48742457: Support role=application web widgets After: The green rect follows web focus. Before: TalkBack tries to read the TV apps as a textual documents. Background: Within ..., screen readers should not consume DPAD (arrow key) events. Web apps or widgets with role=application have, per the WAI-ARIA spec's contract, their own JavaScript logic for moving focus. I learned this through: https://github.com/w3c/aria/issues/1049 Problem: TalkBack completely neglects role=application. Solution: Whenever accessibility focus (the green rect) goes to a WebView with or anywhere within such a document, don't consume the DPAD events; let them through. Testing done: Open a simple TV web app that has . Notice: Once the web view gets accessibilty focus, TalkBack won't eat (consume) DPAD key events and the the key events reach the web page's key handler in JavaScript. Signed-off-by: Hugo Holgersson --- .../TelevisionNavigationController.java | 7 +++++ .../utils/AccessibilityNodeInfoUtils.java | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/talkback/src/main/java/com/google/android/accessibility/talkback/controller/TelevisionNavigationController.java b/talkback/src/main/java/com/google/android/accessibility/talkback/controller/TelevisionNavigationController.java index 51c981344..ab89fa740 100644 --- a/talkback/src/main/java/com/google/android/accessibility/talkback/controller/TelevisionNavigationController.java +++ b/talkback/src/main/java/com/google/android/accessibility/talkback/controller/TelevisionNavigationController.java @@ -361,6 +361,13 @@ private boolean shouldHandleEvent(AccessibilityNodeInfoCompat cursor, KeyEvent e return false; } } + + // Web applications and web widgets with role=application have, per the + // WAI-ARIA spec's contract, their own JavaScript logic for moving focus. + // TalkBack should not consume key events when such an app has accessibility focus. + boolean shouldProcessDPadKeyEvent = this.shouldProcessDPadKeyEvent && + !AccessibilityNodeInfoUtils.isWebApplication(cursor); + // TalkBack should always consume up/down/left/right on the d-pad, unless // shouldProcessDPadKeyEvent is false. Otherwise, strange things will happen when TalkBack // cannot navigate further. diff --git a/utils/src/main/java/com/google/android/accessibility/utils/AccessibilityNodeInfoUtils.java b/utils/src/main/java/com/google/android/accessibility/utils/AccessibilityNodeInfoUtils.java index 1e118cb97..1f2d17de2 100644 --- a/utils/src/main/java/com/google/android/accessibility/utils/AccessibilityNodeInfoUtils.java +++ b/utils/src/main/java/com/google/android/accessibility/utils/AccessibilityNodeInfoUtils.java @@ -381,6 +381,37 @@ public boolean accept(AccessibilityNodeInfoCompat node) { || (node != null && node.getCollectionInfo() != null); }); + public static boolean hasApplicationWebRole(AccessibilityNodeInfoCompat node) { + return node != null && node.getExtras() != null + && node.getExtras().containsKey("AccessibilityNodeInfo.chromeRole") + && node.getExtras().get("AccessibilityNodeInfo.chromeRole").equals("application"); + } + + private static final Filter FILTER_IN_WEB_APPLICATION = + new Filter() { + @Override + public boolean accept(AccessibilityNodeInfoCompat node) { + return hasApplicationWebRole(node); + } + }; + + /** + * Returns true if |node| has role=application, i.e. |node| has JavaScript + * that handles key events. + */ + public static boolean isWebApplication(AccessibilityNodeInfoCompat node) { + // When a WebView has focus: Check Chromium's accessibility tree's first node. + // If that node wants raw key event, instead of first "tabbing" the green + // rect to it, skip ahead and let the web app directly decide where to go. + boolean firstChromiumNodeWantsKeyEvents = + Role.getRole(node) == Role.ROLE_WEB_VIEW + && node.getChildCount() > 0 + && hasApplicationWebRole(node.getChild(0)); + + return firstChromiumNodeWantsKeyEvents || + getSelfOrMatchingAncestor(node, FILTER_IN_WEB_APPLICATION) != null; + } + private AccessibilityNodeInfoUtils() { // This class is not instantiable. }