Skip to content

Commit 1ed4eef

Browse files
Support role=application web widgets
After: The green rect follows web focus. Before: TalkBack tries to read TV apps as text documents. Background: Within <xxx role=application>...</xxx>, 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 [1]. [1] w3c/aria#1049, where we discussed this. Problem: TalkBack does not handle role=application so such web apps lose their 4-way (up/down/left/right) navigation. TalkBack only moves forward/backward which breaks authors' pre-defined TV UX. Solution: Whenever accessibility focus (the green rect) goes to some web content with <body role=application> or anywhere within a role=application widget, we don't consume the DPAD events; we let them through. Testing done: Open a simple TV web app, hosted in an Android WebView, that has <body role=application>. 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.
1 parent 9db811a commit 1ed4eef

2 files changed

Lines changed: 40 additions & 0 deletions

File tree

talkback/src/main/java/com/google/android/accessibility/talkback/controller/TelevisionNavigationController.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,13 @@ private boolean shouldHandleEvent(AccessibilityNodeInfoCompat cursor, KeyEvent e
361361
return false;
362362
}
363363
}
364+
365+
// Web applications and web widgets with role=application have, per the
366+
// WAI-ARIA spec's contract, their own JavaScript logic for moving focus.
367+
// TalkBack should not consume key events when such an app has accessibility focus.
368+
boolean shouldProcessDPadKeyEvent = this.shouldProcessDPadKeyEvent &&
369+
!AccessibilityNodeInfoUtils.isWebApplication(cursor);
370+
364371
// TalkBack should always consume up/down/left/right on the d-pad, unless
365372
// shouldProcessDPadKeyEvent is false. Otherwise, strange things will happen when TalkBack
366373
// cannot navigate further.

utils/src/main/java/com/google/android/accessibility/utils/AccessibilityNodeInfoUtils.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,39 @@ public boolean accept(AccessibilityNodeInfoCompat node) {
381381
|| (node != null && node.getCollectionInfo() != null);
382382
});
383383

384+
public static boolean hasApplicationWebRole(AccessibilityNodeInfoCompat node) {
385+
return node != null && node.getExtras() != null
386+
&& node.getExtras().containsKey("AccessibilityNodeInfo.chromeRole")
387+
&& node.getExtras().get("AccessibilityNodeInfo.chromeRole").equals("application");
388+
}
389+
390+
private static final Filter<AccessibilityNodeInfoCompat> FILTER_IN_WEB_APPLICATION =
391+
new Filter<AccessibilityNodeInfoCompat>() {
392+
@Override
393+
public boolean accept(AccessibilityNodeInfoCompat node) {
394+
return hasApplicationWebRole(node);
395+
}
396+
};
397+
398+
/**
399+
* Returns true if |node| has role=application, i.e. |node| has JavaScript
400+
* that handles key events.
401+
*/
402+
public static boolean isWebApplication(AccessibilityNodeInfoCompat node) {
403+
// When a WebView-like view (an actual WebView or a browser) has focus:
404+
// Check the web content's accessibility tree's first node.
405+
// If that node wants raw key event, instead of first "tabbing" the green
406+
// rect to it, skip ahead and let the web app directly decide where to go.
407+
boolean firstWebNode = WebInterfaceUtils.supportsWebActions(node)
408+
&& !WebInterfaceUtils.supportsWebActions(node.getParent());
409+
boolean firstWebNodeWantsKeyEvents = firstWebNode
410+
&& node.getChildCount() > 0
411+
&& hasApplicationWebRole(node.getChild(0));
412+
413+
return firstWebNodeWantsKeyEvents
414+
|| getSelfOrMatchingAncestor(node, FILTER_IN_WEB_APPLICATION) != null;
415+
}
416+
384417
private AccessibilityNodeInfoUtils() {
385418
// This class is not instantiable.
386419
}

0 commit comments

Comments
 (0)