Technical docs‎ > ‎Accessibility‎ > ‎

Making components accessible (for developers)

This document is a "how-to" guide for ensuring UI components and feature of Android Studio are accessible. The audience is developers of the Android Studio project, specifically when working UI components.

Introduction

In general, there are 3 high levels topics to take into account when ensuring a component/piece of UI is accessible:
  1. Ensuring screen readers have access to names, and sometimes descriptions, of various pieces of UI. See the "Making components accessible" section.
  2. Ensuring screen readers users can navigate through (TAB/Shift-TAB) and interact with various pieces of UI. See the "Managing the input focus" section. 
  3. Ensuring colors used for various pieces of UI are suitable for color blind users. See the "Managing colors" section. See the "Managing colors" section.


Making components accessible

Basically, making components accessible is done by ensuring that all UI components implement the Accessible interface.
  • This should be needed only when implementing a new UI components class, or when directly instantiating a JComponent object.
  • All built-in Swing components already provide a reasonable default implementation.
  • Most IntelliJ platform components already provide a reasonable default implementation.
  • For container, such a JPanel, JList, etc., it is sometimes useful to set an accessible name (AccessibleContexUtil.setName). Screen readers "speak" the name when a component gets the focus. It is also sometimes useful to set the "accessible description" (setDescription). The accessible description is the equivalent of a UI tooltip, in the sense that screen readers will only announce them if the screen readers user explicitly ask for it. The name should be short, the description can be longer.
  • For containers that use the "Cell Renderer" pattern (JList, JTree, JTable), there are special rules on how to make sure the component used to render to current "Cell" is accessible.
See the following section for a (non exhautive) list of components types and what is needed to ensure they offer a reasonable accessiblility behavior.

JLabel

  • Assign the "labelFor" property if the label is associated to another component (e.g. JTextArea is the most common case). There are two ways to do this:
    1. (Recommended) Either call the "setLabelFor" method,
    2. (Limited) Or Set the "labelFor" property in the GUI editor. Note the GUI editor has limited support for complex components, so use call the "setLabelFor" method in code in case the drop-down does not show the component to associate with.

JComponent

It is almost always a bad idea to directly use a JComponent instance, as JComponent does *not* implement the Accessible interface.. However, if using a JComponent directly, you must *always* create and instantiate a sub-class that:
  • Consider using an alternative base class, which often has a default behavior more suited to the context
  • If using a JComponent is still required,
    • Create a derived class that explicit "implements Accessible"
    • Implement a custom AccessibleXxx inner class
      • Override the "getAccessibleRole" to return a pre-defined role
      • (Common) Override the "getAccessibleName" so that screen readers an announce what the component is about
      • See section "Implementing javax.accessibility.Accessible" for a detailed description on the convetion used to implement a custom AccessibleXxx class

JTextArea

  • Consider setting the accessible name if the text area does not have a corresponding JLabel with a "labelFor"

JComboBox

  • Use JBComboBox instead

Box.createVerticalBox/createHorizontalBox

  • Use JBBox.createVerticalBox/createHorizontalBox instead

JPanel

  • (Uncommon): Consider setting an accessible name if it makes the context about the contained component clearer

JTabbedPane

  • Use JBTabbedPane instead
  • or consider using JBTabs instead (more flexible)

JCheckBox

  • Use JBCheckBox instead

JButton

JRadioButton

  • Use JBRadioButton instead

JTextField

  • Use JBTextField instead

JBTextField

JList

  • Use JBList instead

JBList

  • Consider calling "AccessibleContextUtil.setName" to ensure the list has a descriptive name, especially if there is no JLabel associated to the list
  • (Common) If using a custom cell renderer
    • Ensure cell renderer component is accessible, i.e. apply the same rules defined in this document recursively to the cell renderer component sub-tree.
    • If the renderer component is a complex component (e.g. a JPanel containing other components), ensure the renderer component has a name summarizing the content of the list element (so that screen readers announce the name as the user moves the selection inside the list).
    • TODO: Add link to example

JTree

  • Use Tree instead

Tree

  • (Common) Follow the same rules as JBList: call AccessibleContextUtil.setName and ensure cell renderer component is accessible
  • If items are editable,
    • Ensure the cell editor is accessible via the keyboard (usually "SPACE" key to start editing)
  • Note: If using a complex hierarchy of component for cell rendering, screen readers will only be able to "see" the top-level component of the sub-tree.
    • This is a limitation of the Java Accessibility API, so there is no known workaround
    • For example, if the root element of the cell renderer component is a JPanel that contains many sub-elements with text, screen readers won't see any text. In this case, the JPanel should be assigned an accessible name, ideally containing the text of all the sub-components.
    • For complex component that require user interaction (for example, hyperlink in graddle error message), there is currently no solution to make it accessible

JTable

  • Use JBTable instead

JBTable

JBTable are complex to make accessible, because 
  • Follow the same rules as JBList: call AccessibleContextUtil.setName and ensure renderer component is accessible
  • Ensure the TAB/Shift-TAB keys move to the next component, as opposed to the next cell
    • Note: This should work by default, because JBTable overrides the default Swing behavior
  • If the cells/rows support user interaction, things can get complicated very quickly
    • The simplest cases is when only a single cell allows editing (for example, one of the column contains a checkbox
      • In that case, ensure the "space" key works from any cell in the row to start the editing
    • If multiple columns are editable, ensure that the cursor keys allows navigating to each cell and that pressing "space" allows editing each cell content
  • Ensure IntelliSearch works when keys are typed to searchs for cells in the table

TreeTable

TreeTable is a combination of a JBTable where one of the column contains a Tree, so it is quite a complex piece of UI.
  • Ensure that all requirements concerning JBTable and Tree are met

Creating new custom component classes

There are no generic rules, but a few notable things:

  • Ensure there is no alternative in Swing and the IntelliJ platform before implementing a custom component, as there will be work required to make the new component accessible.
  • It is very likely that the new component also needs a custom AccessibleXxx inner class implementation. The methods to override depend on the nature of the custom component
  • Ensure the base class, if coming from the IntelliJ platform, already implements a custom AccessibleXxx interface. If the base component is not fully accessible, it likely must be fixed first.
  • Ensure the component is usable with keyboard only (if interactive). For example, when adding a custom mouse click handler, it is very likely a custom keyboard handler is also needed.
  • Tab/Shift-TAB should not be overridden so that the focus can move to the next/previous component
  • The Space key should be used for the "main action" of the component (e.g. button pressed for a button)

Managing the Input focus and the keyboard

In general, when creating a new form/tool window/etc, the goal is to ensure each "interactive" component is reachable via the TAB/Shift-TAB keys, as well as making sure these component are actionable via keyboard (e.g. the SPACE key activates a button).
  • See https://docs.oracle.com/javase/tutorial/uiswing/misc/focus.html for a general description of the Swing focus sub-system
  • Do *not* call "setFocusCycleRoot(true)" unless creating a new kind of top-level Frame/Dialog, as this prevent TAB/Shift-TAB from moving "out" of the container. Instead use "setFocusTraversalPolicyProvider" and "setFocusTraversalPolicy" if absolutely needed.
  • Do not override "requestFocus" to move the focus to another control. Use a focus policy instead.
    • This is because overriding "requestFocus" will not catch the cases where the focus changes because of a TAB/Shift-TAB focus traversal. "requestFocus" is invoked when
      • the component is focusable *and*
      • the user clicks the component, or a call to "requestFocus" is made by some Java code
  • Call "setFocusable(true)" for any component than need to be accessible via the TAB/Shift-TAB focus traversal keys. If a component is contained in a JPanel, there is no need to call "setFocusable(true)" on the JPanel, unless the JPanel itself wants to receive the input focus and be reachable via TAB/Shift-TAB.
  • Order component creation in the top-left to bottom-right order of the UI. This is because screen readers have an option to read the full content of a window, and they rely on the order of components returned by "getComponent".
    • Note: This is sometimes difficult to achieve, because the IntelliJ GUI builder does not allow re-ordering components from the UI. Editing the .form XML file is necessary in such cases.
  • Optional: Define key bindings for activating menu items, tool window elements, etc. This makes keyboard navigation easier for screen reader users.
    • The issue being that there are already many key bindings in use in Android Studio, so it can be difficult to find "free" slots.

Managing colors

Ensuring that anytime a custom color is used, it works with the various IntelliJ themes (Darcula, etc.). Swing does not provide support for color blind users by default, so it is the responsibility of the application to support color blind users. Fortunately, Android Studio already supports themes, so there is generally no additional work if using colors defined in the IntelliJ platform. Using custom colors requires making sure if it can be customized and it changes depending on the active theme.

Tools

  • To help figuring out the accessibility feature of a component
    • Tools | Internal Actions | UI | UI Inpector
      • Activate the menu, then use "Ctrl-Alt-Click" to show a tree view of the component at the mouse cursor location. The "properties" pane on the right shows the component properties, including a subset of "AccessibleContext"
        • This can be useful ot know if a component implements Accessible and what info is returned from "getAccessibleContext"
    • Access Bridge Explorer
      • Ensure the "Options | Component Overlay | Enabled" option is checked
      • Ensure the "Options | Component Overlay | Hook Ctrl+\ to capture" option is checked
      • At any point in time, move the mouse cursor over a component to capture. Hit the "Ctrl+\" key. This will highlight the component in the Accessibility Tree and show the content a getAccessibleContext in the right properties pane. This shows *all* the information returned by AccessibleContext, including optional interface, etc.
  • Tools to help debugging issues related to focus management
    • Tools | Internal Actions | UI | Start Focus debugger
      • This activates a green box around the component with the input focus, and a red box around the component that previously had the input focus.
    • Tools | Internal Actions | UI | Start Focus Tracing (Ctrl-Shift-F11)
      • Starts recording focus events. Invoke again (with Ctrl-Shift-F11) to stop recording focus events and display a window containing the list of events since start.
    • Tools | Internal Actions | UI | UI Inpector
      • Activate the menu, then use "Ctrl-Alt-Click" to show a tree view of the component at the mouse cursor location
        • This is particularly useful if you are not familiar with the code related to some UI component and want to know more about, including figure out the component class/package name.
    • Access Bridge Explorer
      • Select Options | Component Overlay | Enable (or Ctrl-Shift-\ to toggle)
      • Select Options | Component Overlay | Activate on Focus Events
      • Select Options | Component Overlay | Activate on Active Descendant Events
      • Select Options | Component Overlay | Synchronize Tree
      • With all these options enabled, Access Bridge Explorer highlights the current input focus component with a semi-transparent box, 


Implementing a custom javax.accessibility.Accessible class

By default, standard components from Swing and the IntelliJ platform provide reasonable support for screen readers. When creating custom component classes
From an implementation perspective, accessibility support for Swing is provided through the Accessible interface: all components that support accessibility must implement AccessibleAccessible contains a single entry point getAccessibleContext that returns a class derived from the pre-defined AccessibleContext abstract class. AccessibleContext serves as the entry point for providing all the information required for screen readers to help visually impaired users use Swing based applications.

By convention, the custom implementation of AccessibleContext is an inner class of the component class. For example, the JLabel class implementation is along the lines of:

public class JLabel extends JComponent implements Accessible {
// Note: getAccessibleContext is typically only called when a screen reader
// is active and requires information about a UI component
public AccessibleContext getAccessibleContext() {
// Note: By convention, all implementations follow this lazy-initialization pattern
// so that the memory overhead of the accessible context instance is paid only when/if
// a screen reader explicitly requires accessibility information for the given
// component instance.
if (accessibleContext == null) {
accessibleContext = new AccessibleJLabel();
}
return accessibleContext;
}

// Note: By convention, this is a inner class named "AccessibleXxx" where
// "Xxx" is the name of the outer component class ("JLabel" in this case).
//
// Note: It is recommended the inner class inherits from the inner class of the
// component base class ("AccessibleJComponent" in this case). This ensures
// the accessibility behavior of the base class is inherited.
protected class AccessibleJLabel extends AccessibleJComponent {
// Note: Override methods here to provide customized behavior. JLabel, for example,
// overrides getAccessibleRole to return AccessibleRole.LABEL.
}
}

Comments