a11y

Making your Android Apps More Inclusive with accessibilitools

Ataul is a Google Developer Expert for Android focusing on inclusive design for mobile platforms and loves facilitating the production of universally useable apps. Also, he watches a lot of movies.

accessibilitools 1.4.0 was released a few weeks ago. In this post, I'll share what this small set of classes consists of and how you can use it to create more inclusive Android apps.

accessibilitools is a tiny collection of utilities to help the production of accessible Android apps. Since a lot of the work we've been doing at Novoda over the past year has been focused on support visually impaired users, so too is the library focused (for now!).

I'm excited, let's get started.

Determine state of accessibility services

One of the things I try to explain to folks is that it's not necessary to provide the same experience for every user of your product; user journeys can be different but still achieve the same goal.

To this end, it's useful to be able to detect whether a user has an accessibility service enabled, like TalkBack or the captioning preference.

AccessibilityServices a11yServices = AccessibilityServices.newInstance(context);  
if (a11yServices.isSpokenFeedbackEnabled()) {  
    // show UI
} else {
    // show different UI
}

You can use it to display (or hide) affordances from one set of users. In the example, we show a "dismiss controls" button in addition to the regular player controls.

sketch showing two sets of video player controls. left: with dismiss button, right: no dismiss button

Setting usage hints

TalkBack is an accessibility service that helps render the UI in spoken word. One of the things it does is to help the user understand when a UI element is actionable.

For example, when reading aloud the description of a button, it will append "Double tap to activate", a usage hint that tells the user what gesture is necessary and what action will be performed. For custom views or other view groups, it'll rely on the content description you set.

sketch showing card with image, play icon and text

In general, you'll use it to describe the element itself, not what action it will perform. You can customise the usage hint (separate from the content description) so it fits your app. The way to do it is to override the label for the click event in the View's accessibility node.

@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {  
    super.onInitializeAccessibilityNodeInfo(host, info);

    info.addAction(new AccessibilityActionCompat(ACTION_CLICK, "start playback"));
}

Now it'll say "Double tap to start playback"! With accessibilitools, you can do it in a few lines using a class called UsageHintsAccessibilityDelegate. This helps you override the default labels for click and long click events:

UsageHintsAccessibilityDelegate delegate = new UsageHintsAccessibilityDelegate(resources);  
delegate.setClickLabel("start playback");

ViewCompat.setAccesibilityDelegate(buttonView, delegate);  

Actions dialog

A lot of the time, we include inline actions on our list items. This makes it more difficult for TalkBack or keyboard users to navigate between items since the extra icons are focusable.

sketch showing three versions of Video in list. Top left: with image, text, play button, mark watched icon, favourite icon and download icon. Top right: image and text. Bottom: just text

Here we move from a visually and cognitively busy element to one which is truly bare bones; for a TalkBack user, it could be simplified to just a card with some text describing the video.

In this case, there's a single active area and we're able to pop a dialog containing all the actions for that particular item.

private final ActionsAlertDialogCreator dialogCreator;

@Override
public void bindViewHolder(ViewHolder vh, int position) {  
    Actions actions = createActionsForItemAt(position);
    final Dialog actionsDialog = dialogCreator.create(actions);

    vh.itemView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            actionsDialog.show();
        }
    });

    // bind rest of view
}

private Actions createActionsForItemAt(int position) {  
    final Video video = videos.get(position);
    return new Actions(Arrays.asList(
        new Action(R.id.play, R.string.play, new Runnable() {
            @Override
            public void run() {
                listener.onClickPlay(video);
            }
        }),
        new Action(R.id.mark_watched, R.string.mark_watched, new Runnable() {
            @Override
            public void run() {
                listener.onClickMarkWatched(video);
            }
        }),
        new Action(R.id.download, R.string.download, new Runnable() {
            @Override
            public void run() {
                listener.onClickDownload(video);
            }
        }),
        new Action(R.id.favorite, R.string.favorite, new Runnable() {
            @Override
            public void run() {
                listener.onClickFavorite(video);
            }
        })
    ));
}

Custom accessibility actions

Did you know that TalkBack allows you to specify the actions on a View, and these are read aloud along with usage hints? It also makes these available from the custom actions menu, available to TalkBack users via a local context menu.

sketch of radial menu showing

Here's how we would have to do it previously, again, overriding the onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) method in View:

@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {  
    super.onInitializeAccessibilityNodeInfo(host, info);
    info.addAction(new AccessibilityActionCompat(R.id.play, R.string.play));
    // ... same for all other actions
}

@Override
public boolean performAccessibilityAction(View host, int actionId, Bundle args) {  
    // map up the actionId, and fire a callback for the correct position
}

accessibilitools includes the ActionsAccessibilityDelegate class so you can do it in two lines:

@Override
public void bindViewHolder(ViewHolder vh, int position) {  
    ...

    ActionsAccessibilityDelegate delegate = new ActionsAccessibilityDelegate(getResources(), actions);
    ViewCompat.setAccessibilityDelegate(this, delegate);

    // bind rest of view
}

ActionsAccessibilityDelegate also lets you set the usage hints for click and long click events, so you can make the behaviour clear to users.

Give it a go!

Please try it out and let us know if it's been helpful in making your Android app more accessible.

If you encounter any bugs or have any questions, you can open an issue on the GitHub page, otherwise you can reach out to me directly on Twitter.

Enjoyed this article? There's more...

We send out a small, valuable newsletter with the best stories, app design & development resources every month.

No spam, no giving your data away, unsubscribe anytime.

About Novoda

We plan, design, and develop the world’s most desirable software products. Our team’s expertise helps brands like Sony, Motorola, Tesco, Channel4, BBC, and News Corp build fully customized Android devices or simply make their mobile experiences the best on the market. Since 2008, our full in-house teams work from London, Liverpool, Berlin, Barcelona, and NYC.

Let’s get in contact

Stay in the loop!

Hear about our events, blog posts and inspiration every month

Subscribe to our newsletter