While many Android TV apps are built with native Android components, it's also important to consider the accessibility of third-party frameworks or components, especially when using custom views.
Custom view components interfacing directly with OpenGL or Canvas might not work well with accessibility services like Talkback and Switch Access.
Consider some of the following issues that might occur with Talkback switched on:
- The accessibility focus (a green rectangle) might disappear in your app.
- The accessibility focus might select the boundary of the whole screen.
- The accessibility focus might not be movable.
- The four direction keys on the D-pad might have no effect, even if your code is handling them.
If you observe any of these issues in your app, check that your
app exposes its
tree to the accessibility services.
The remainder of this guide provides some solutions and best practices to address these issues.
D-pad events are consumed by accessibility services
The root cause of this issue is that key events are consumed by accessibility services.
As illustrated in figure 1, when Talkback is switched on, D-pad events are not passed to the D-pad handler defined by developer. Instead, accessibility services receive the key events so they can move the accessibility focus. Because custom Android components don't by default expose information to accessibility services about their position on the screen, accessibility services can't move the accessibility focus to highlight them.
Other accessibility services are similarly affected: D-pad events might also be consumed when using Switch Access.
Because D-pad events are submitted to accessibility services, and
that service doesn't know where UI components are in a custom view,
you must implement
AccessibilityNodeInfo for your app to forward the
key events correctly.
Expose information to accessibility services
To provide accessibility services with sufficient information about the
location and description of custom views, implement
to expose details for each component.
To define the logical relationship of views so that accessibility services can
manage focus, implement
and set it using
for custom views.
ExploreByTouchHelper, override its four abstract methods:
// Return the virtual view ID whose view is covered by the input point (x, y). protected fun getVirtualViewAt(x: Float, y: Float): Int // Fill the virtual view ID list into the input parameter virtualViewIds. protected fun getVisibleVirtualViews(virtualViewIds: List<Int>) // For the view whose virtualViewId is the input virtualViewId, populate the // accessibility node information into the AccessibilityNodeInfoCompat parameter. protected fun onPopulateNodeForVirtualView(virtualViewId: Int, @NonNull node: AccessibilityNodeInfoCompat) // Set the accessibility handling when perform action. protected fun onPerformActionForVirtualView(virtualViewId: Int, action: Int, @Nullable arguments: Bundle): Boolean
// Return the virtual view ID whose view is covered by the input point (x, y). protected int getVirtualViewAt(float x, float y) // Fill the virtual view ID list into the input parameter virtualViewIds. protected void getVisibleVirtualViews(List<Integer> virtualViewIds) // For the view whose virtualViewId is the input virtualViewId, populate the // accessibility node information into the AccessibilityNodeInfoCompat parameter. protected void onPopulateNodeForVirtualView(int virtualViewId, @NonNull AccessibilityNodeInfoCompat node) // Set the accessibility handling when perform action. protected boolean onPerformActionForVirtualView(int virtualViewId, int action, @Nullable Bundle arguments)
For more details, watch Google I/O 2013 - Enabling Blind and Low-Vision Accessibility on Android or read more about populating accessibility events.
AccessibilityNodeInfo.getBoundsInScreen()must define the position of the component.
AccessibilityNodeInfo.setVisibleToUser()must reflect the visibility of the component.
AccessibilityNodeInfo.getContentDescription()must specify the content description for Talkback to announce.
AccessibilityNodeInfo.setClassName()so services can distinguish the component type.
performAction(), reflect the action using a corresponding
To implement more action types, such as
AccessibilityNodeInfo.addAction(ACTION_CLICK)using the corresponding logic in
When applicable, reflect the component state for
setScrollable(), and similar methods.
Review the documentation for
AccessibilityNodeInfoto identify other ways in which accessibility services can better interact with your components.
Consult the custom view accessibility sample for Android TV to see best practices for adding accessibility support to apps using custom views.