Android apps should be usable by everyone, including people with disabilities. Common disabilities that affect a person's use of an Android device include blindness or low vision, deafness or impaired hearing, restricted motor skills, and color blindness. And this is just a partial list.

Apps written with accessibility in mind make the user experience better for everyone: keyboard shortcuts in Gmail help power users, high contrast helps when looking at a screen with glare in the background, and voice controls help you control your device when you're cooking.

By working through this codelab, you will gain knowledge about how people with certain kinds of disabilities use Android applications, and you will learn how to write applications that enrich the experience for these users.

Audience and Prerequisites

This codelab is intended for Android developers who want to understand how to make their apps accessible to users with disabilities.

This codelab assumes the following:

  1. Basic familiarity of writing Android applications
  2. Access to an Android device running Jelly Bean (API level 16) or higher. For I/O attendees, a device is already made available to you.
  3. Availability of Google Talkback on your device. Talkback comes pre-installed on many Android devices (including all devices available to I/O attendees).
  4. Availability of Accessibility Scanner (already installed on all devices available to I/O attendees).

Objectives

In this codelab, you will:

The codelab is structured in small, discrete steps, and each step focuses on a specific accessibility issue.

Download the Code

You can clone the repo that contains the code for this codelab:

git clone https://github.com/googlecodelabs/basic-android-accessibility.git

Or you can download the code by clicking the following button and unpacking the zip file:

Download source code

Using a terminal, change into the root directory.

Git branches

The code is organized in two branches, the master branch, and the accessible branch.

Each step in this codelab is structured so that you start working with an activity or a feature that has been implemented in an inaccessible manner. By the end of each step, you will have modified the code, and you will have made the activity of feature accessibile.

Setting up Android Studiosites

Launch Android Studio by clicking the Studio icon:

Select the Import project (Eclipse ADT, Gradle, etc.) option:

Navigate to the location where you unzipped the source, and select basic-android-accessibility.

Your Android studio screen should now look like this:

Expose the directory structure of the application using the following steps:

  1. Expand app/ in left column.
  2. Expand java/ and res/.
  3. Under res/, expand layout/.

All source files are located under app/java, and all layout files appear under app/res/layout.

The Android Studio screen with the expanded directories should look something like this:

Launching the demo application

Make sure a device is connected to your workstation. Press the green Play icon from the menu bar towards the top of the screen. This should launch the Basic Android Accessibility Codelab app. The landing page for that app looks like this:

Don't close the app. We'll be returning to it really soon.

In this step, we'll be setting up Google Talkback.

TalkBack is Android's bundled accessibility solution. It is a screen reader that offers auditory, haptic, and spoken feedback, which allows users to navigate and consume content on their devices without using their eyes. Using TalkBack is a great way to become acquainted with the ways in which someone with visual impairments might access content on their device, and it's also a great way to test the accessibility of your own application.

First, change your device's settings so that using TalkBack isn't disruptive to other codelab attendees:

TalkBack Settings

We'll use TalkBack using a developer-mode option that will show the text from the auditory feedback on the screen.

Follow these steps to set up TalkBack. These instructions assume a Nexus device; the location of TalkBack settings can vary on other Android devices.

Turning TalkBack On

TalkBack tutorial

At this point, TalkBack may launch its Tutorial, which goes over TalkBack fundamentals. You are welcome to work through the tutorial, but you can skip it for now if you prefer.

We use only a subset of Talkback's functionality in this codelab, and the next step provides enough help to get you started with Talkback.

In the previous step, we configured and turned on TalkBack. In this step, we'll cover basic navigation using TalkBack.

Basic Touch Explore

  1. Tap the Overview button. This is the square button on the bottom right of your screen. TalkBack announces the selected button (it draws a green rectangle around it to show that the button has focus).

  1. Double-tap anywhere on the screen to select the focused item.

This two-step process is integral to exploring Android apps by touch. TalkBack announces icons, buttons, and other items as you touch them. You double-tap on the screen to select the focused item.

If you did not close the codelab app before starting Step 1, your screen should look something like this:

To start using the codelab app, do the following:

  1. Tap on the codelab screen. TalkBack announces that it has focus.
  2. Double-tap to return to the codelab activity.

Linear navigation

To explore your screen one item at a time, swipe-left or swipe-right to move through the items in sequence. Swipe-left takes you to the item previous from the one you're on. Swipe-right takes you to the next item.

Execute the following steps:

  1. Click on the "Basic Android Accessibility Codelab" title in the toolbar at the top of the screen. TalkBack focuses on this view.
  2. Swipe-right. The focus moves to "CONTENT LABELING ACTIVITY".
  3. Swipe-right again. The focus now moves to "CONTENT GROUPING ACTIVITY".
  4. Swipe-left. The focus moves back to "CONTENT LABELING ACTIVITY".

TalkBack offers a rich option of gestures (learn more about TalkBack gestures), but in this codelab we'll restrict ourselves to the gestures covered in this step.

For help with TalkBack, visit https://support.google.com/talkback/.

When a user with vision impairment tries to navigate an application by touch, TalkBack announces all actionable content as long as it has meaningful, useful, and descriptive labels. If such labels are missing, TalkBack may not be able to properly explain the function of a particular control to a user. In some cases, TalkBack may skip over some content may entirely.

In this step, you'll explore how TalkBack handles inadequately labeled content. Once you improve the labeling, you'll see how that improves the experience of the TalkBack user.

Unlabeled Content

Navigate to Content Labeling Activity (remember this is a two-step process when using TalkBack). The screen looks like this:

The activity contains a title, an EditText for entering text, "Go" button, a Share button, and Play button. When the play button is clicked, it toggles to a Pause button.

Discover the content on the screen using TalkBack:

  1. Tap on the title. TalkBack announces "Content labeling activity".
  2. Swipe right to get to the next view. TalkBack announces "Jukebox".
  3. Swipe right again. TalkBack skips over the purely decorative musical note image (we'll discuss that shortly), focuses on the EditText, and announces "Edit box. Enter favorite song." Notice that the value of the android:hint attribute we use with the EditText is picked up TalkBack.
  1. Swipe right to focus on the Go button. TalkBack picks up the text associated with the button and announces "Go button", and then adds "Double tap to activate." This tells the user how to interact with the view.
  2. Swipe right to focus on the Share button. TalkBack announces "Button, Unlabelled". This isn't very helpful, but we haven't given TalkBack much to work this. We'll fix that soon.
  3. Swipe right to focus on the Play button. TalkBack similarly announces "Button, Unlabelled". When you double-tap the Play icon, it changes to a Pause icon. When you tap on the icon again, TalkBack again announces "Button, Unlabelled". The button toggles state, but the TalkBack user has no way of knowing anything about the button state. We'll fix this soon.

Properly labeled content

Let's improve the experience for the TalkBack user by properly labeling content.

Fixing the decorative image

Go back to Android Studio and from res/layout, open content_labeling.xml.

Place your cursor over the first <ImageView>. Android Studio's lint checks warn about a missing contentDescription.

Let's fix this. Since the musical note is purely decorative, we should set the contentDescription to "@null":

<ImageView
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:layout_weight="1"
   android:contentDescription="@null"
   android:src="@drawable/ic_music_note" />

This satisfies lint that that this view really has no contentDescription (and not that you simply neglected to add a necessary contentDescription).

Fixing the Share image button

TalkBack simply reads the Share image button as "Button, Unlabelled", and Android Studio generates a lint warning about a missing contentDescription. Add a contentDescription, using "@string/share" as the value (this is defined in res/values/strings.xml with a value of "Share"):

<ImageButton
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_alignParentBottom="true"
   android:layout_alignParentLeft="true"
   android:layout_alignParentStart="true"
   android:contentDescription="@string/share"
   android:src="@drawable/ic_share" />

Press the green Play icon from the menu bar to run the app (TalkBack announces that activity was restarted, which is helpful).

Tap on the Share icon. Talkback now announces, "Share Button".

Fixing the Play/Pause button

The Play/Pause toggle is managed dynamically through the activity Java code. Look at the code in ContentLabelingActivity.java:

private void updateImageButton() {
    if (mPlaying) {
       mPlayPauseToggleImageView.setImageResource(R.drawable.ic_pause);
    } else {
       mPlayPauseToggleImageView.setImageResource(R.drawable.ic_play_arrow);
    }
}

We toggle the image, but don't add a contentDescription to describe the current state of the view. Fix the code, so it looks like this:

private void updateImageButton() {
    if (mPlaying) {
       mPlayPauseToggleImageView.setImageResource(R.drawable.ic_pause);
       mPlayPauseToggleImageView.setContentDescription(getString(R.string.pause));

    } else {
       mPlayPauseToggleImageView.setImageResource(R.drawable.ic_play_arrow);
       mPlayPauseToggleImageView.setContentDescription(getString(R.string.play));
    }
}

Press the green Play icon from the menu bar to run the app and navigate to the Play icon. TalkBack announcements are much more meaningful now:

  1. Tap on the Play icon. TalkBack announces "Play button".
  2. Double-tap to toggle the icon.
  3. Tap on the Pause icon. TalkBack announce "Pause button".

Helping D-Pad users

Prior to Android 4.0, apps needed to be accessible via D-Pad. To help D-Pad users, you may need to manually specify that some clickable items (e.g. ImageViews) are focusable using android:focusable="true". Since by default, content is not focusable, you do not need to add android:focusable="false" to purely decorative views like the music note ImageView.

Sometimes the auditory feedback TalkBack gives for visual elements in an app may not reflect their logical and spatial structure. Even though elements may be ordered in a sensible way visually, they may be spoken out of order.

Incorrectly Grouped Content

To see an example for this, navigate to the Content Grouping Activity.

Click on the title, and swipe right repeatedly to move down to the other views. What do you notice?

TalkBack announces the title ("Song Details"), and then it announces the views in the following order:

  1. "Name"
  2. "Hey Jude"
  3. "Artists"
  4. "The Beatles"
  5. "Cost"
  6. "$1.45".

While the TalkBack user is able to discover all the content on the screen, the user has to do a lot of swiping. If the song details had many more fields, the experience would quickly become tedious.

Grouping Content for Accessibility

There are several ways to fix this. Since the song data is made up of only six short strings, we could have TalkBack group all six items into a single announcement. Let's experiment with this approach.

It is a common best practice to group non-focusable items (e.g. TextViews) in a focusable container to have them read as a single item.

Open content_grouping.xml and locate the <RelativeLayout> that contains the six <TextView>s that we wish to consolidate.

<RelativeLayout
   android:id="@+id/song_data_container"
   android:layout_width="match_parent"
   android:layout_height="wrap_content">
...
</RelativeLayout>

Make the <RelativeLayout> focusable:

<RelativeLayout
   android:id="@+id/song_data_container"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:focusable="true">
...
</RelativeLayout>

Press the green Play icon from the menu bar run your code and try out the new version of Content Grouping Activity with TalkBack.

What do you notice now? TalkBack announces the title ("Song Details"), and then it announces the song details as a single announcement ( "Name, Hey Jude, Artists, The Beatles, Cost, $1.45").

Using natural groupings

In the example we're considering, having a single TalkBack announcement is better than having six, but this solution has its limits:

  1. It quickly becomes unscalable if the number of song detail fields increases, or if the fields contain a lot of text.
  2. Neither the original six-announcement version, nor the single-announcement version we just implemented take the natural grouping of views into consideration. The data is clearly visually organized in two columns - the items on the left and the corresponding values on the right. This grouping is evident to the user who can see the screen. You should ensure this is just as obvious for a user with visual impairment using TalkBack.

You will now reimplement this functionality by having TalkBack announce the song details a single row at a time.

This requires placing the "Name" and "Hey Jude" TextViews into a single focusable ViewGroup, placing the "Artists" and "The Beatles" into another focusable viewgroup, and so on. To make TalkBack announce content by row, your XML will need to look something like this:

<LinearLayout
    ...
    orientation="vertical">

    <RelativeLayout
        ...
        android:focusable="true">

        <TextView />
        <TextView />
    </RelativeLayout>

    <RelativeLayout
        ...
        android:focusable="true">

        <TextView />
        <TextView />
    </RelativeLayout>

    <RelativeLayout
        ...
        android:focusable="true">

        <TextView />
        <TextView />
    </RelativeLayout>
</LinearLayout>

Replace the contents of content_grouping.xml with this version of that file that fills out the hierarchies outlined above.

Press the green Play icon from the menu bar run your app and try out Content Grouping Activity once again with TalkBack. The song details are now announced like this:

  1. "Name, Hey Jude"
  2. "Artists, The Beatles"
  3. "Cost, $1.45"

So far, all the examples have involved using Talkback with views that a user has explicitly focused on. But sometimes, you need to discover text that updates dynamically without having to explicitly navigate to it and focus on it.

Return to MainActivity using the Back button, and navigate to Live Region Activity.

This activity show a multiple choice question. Here's how Talkback works with radio buttons:

  1. When you focus on a radio button, TalkBack announces the button text and its checked status ("not checked, JellyBean Radio button", for example).
  2. When you double-tap to select a button, TalkBack announces the changed button state ("JellyBean, checked", for example).

Whenever you make a selection, text at the bottom of the screen informs you whether you picked correctly or incorrectly. To the user who can see the screen, this helpful feedback is immediately available. But TalkBack doesn't automatically announce this feedback. To instruct TalkBack to do so, we need to to use a live region.

Open content_live_region.xml and locate the TextView responsible for the correct/incorrect feedback.

<TextView
   android:id="@+id/feedback_text_view"
   android:layout_width="match_parent"
   android:layout_height="wrap_content" />

Add an accessibilityLiveRegion attribute to this view, giving it a value of "polite" :

<TextView
   android:id="@+id/feedback_text_view"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:accessibilityLiveRegion="polite" />

Press the green Play icon from the menu bar to run the app. Try using LiveRegionActivity again.

When you focus on a radio button and double-tap to select it, TalkBack announces the changed button state and also gives you feedback about whether you were right nor not ("JellyBean, checked. Incorrect", or "Lollipop, checked. Correct!", for example).

How live regions work

Using an accessibilityLiveRegion with a view means that when that view updates, it generates an extra AccessibilityEvent in the event stream that an AccessibilityService like TalkBack can pay attention to.

Being "polite"

We gave our accessibilityLiveRegion a value of "polite". This simply means that TalkBack won't interrupt anything it may already be announcing when it processes the event generated by a live region. An alternate to "polite" is "assertive", which instructs TalkBack move announcements related to a live region up the event queue and interrupt ongoing announcements. You should use live regions sparingly, and nearly always avoid "assertive" live regions.

ViewCompat and backwards compatibility

Go back to content_live_region.xml and look at the android:accessibilityLiveRegion="polite" line you just added. While this fixed the problem of the missing feedback for TalkBack users, Android studio warns us that accessibilityLiveRegion is only available in API level 19 ("KitKat") and higher (we set the minSdkVersion in our demo app to 16).

Fortunately, we can use the ViewCompat class to access features like live regions in a backward compatible way. Do the following:

  1. In content_live_region.xml, remove the android:accessibilityLiveRegion="polite" line you just added.
  2. Open LiveRegionActivity.java and add the following to the bottom of the onCreate() method:
  3. Locate the following line of code for finding the feedback textview:
mFeedbackTextView = (TextView) findViewById(R.id.feedback_text_view);

Immediately below, add the following:

if (mFeedbackTextView != null) {
   ViewCompat.setAccessibilityLiveRegion(mFeedbackTextView,
           ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);
}

Press the green Play icon from the menu bar run the app again, and try out the functionality in LiveRegionActivity. The live region now works as desired, and it works on older versions of Android, too.

You will notice that the solutions to accessibility problems discussed in this codelab are quite straightforward - adding contentDescriptions, grouping related UI elements in the same container, etc. This is mostly because default Android UI components have accessibility "baked in" - that is to say, there is useful metadata in the code for buttons, switches, checkboxes, etc. that tells a service like TalkBack how to speak these components out loud.

When you create your own custom views instead of using the default ones in the framework, the accessibility problems quickly become more difficult and the solutions become non-trivial.

Covering accessibility of custom views is outside the scope of this codelab, but it is worth touching on the subject briefly.

Here are some APIs for making custom views accessible:

We've used TalkBack to discuss accessibility features for visually impaired and blind uses for every example so far. We switch gears now and talk about accessibility more broadly. We won't be using TalkBack any more in this codelab and should disable this tool. First, we need to find our way back to the Accessibility Settings screen. Follow these steps:

  1. Tap the Overview Button on the screen's bottom right, and double-tap to see recent applications.
  2. Tap on Settings, and double-tap visit the Settings page.
  3. Use two fingers to scroll down to Accessibility.
  4. Tap on Accessibility, and double-tap to enter the Accessibility screen.

To turn off TalkBack, follow these steps:

  1. Tap on TalkBack, and double-tap to enter the TalkBack screen.
  2. Tap on the Switch button, and double tap to turn it off.
  3. Tap on "OK" in the dialog that pops up, and double-tap to dismiss the dialog and turn off TalkBack.

Many people have difficulty focusing on small touch targets on the screen. This could be because their fingers are large, or they have a medical condition that impairs their motor skills. Small touch targets also make it harder for screen reader users to navigate apps using explore by touch.

Open Expand Touch Area Activity. This activity contains a single button, which toggles between Play and Cancel states. The button is small (24dp X 24dp) and has only 1/4th the touchable area than the 48dp X 48dp buttons we have used in other activities in this codelab. Small touch targets are easy for anyone to miss, and in general you want the touchable area of focusable items to be a minimum of 48dp X 48dp. Larger than that is even better.

In this step, you'll expand the touchable area of the play/cancel button without changing its appearance. But before we change anything, let's get a better sense of just how much area the button actually takes.

Tooling and layout bounds

Go to Settings -> Developer Options. Under the Drawing category, find "Show layout bounds" and turn it on. Your screen should now show the clip bounds, margins, etc. of every visible view.

Go back to Expand Touch Area Activity, and notice the small layout bounds for the button.

In Android Studio, open content_expand_touch_area.xml, and look at the XML for the image button:

<ImageButton
   android:id="@+id/play_pause_toggle_view"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_centerInParent="true"
   android:background="@null"
   android:contentDescription="@null" />

Add a minimum width and height to the ImageButton:

<ImageButton
   ...
   android:minWidth="48dp"
   android:minHeight="48dp" />

Press the green Play icon from the menu bar to run the app, and look at the layout bounds of the button again. What do you see?

The layout bounds have expanded, making the touchable area 48dp X 48dp, our recommended minimum target.

Try rapidly clicking on the button to toggle between the play and cancel icons, and you'll find that the expanded touch target makes it easier to avoid missing the button.

Go back to Settings -> Developer Options -> Drawing category, find "Show layout bounds" and set it to "off".

Users with low vision can't read information on a screen if there is not enough contrast between the foreground and background. Low contrast ratios between foreground and background colors can cause views to blur together for some users, while high contrast ratios makes them stand out more clearly. Different lighting situations can amplify the problems created by low contrast rations.

In this step, you'll see two versions of an activity. One version uses low contrast between background and foreground, and the other version uses high contrast. There's no coding component in this step. Instead we'll be discussing the contrast ratios used in the two versions, and discuss why one works for accessibility and one does not.

Open Insufficient Contrast Activity, which opens with the default low-contrast version. The screen displays three views—a title, some text, and a Floating Action Button—and these are displayed against a light gray background using the following ratios:

Background

View color

Contrast ratio

Lorem Ipsum Title

#E0E0E0

Light gray (#BDBDBD)

1.42:1

Lorem Ipsum Text

#E0E0E0

Medium gray (#757575)

3.49:1

Floating Action Button

#E0E0E0

Light indigo (#7986CB)

2.61:1

The contrast on all three views is inadequate.

Use the Switch button at the bottom of the screen to toggle to the higher-contrast version of the same screen. This version introduces the following changes:

  1. Overall background: light gray is replaced with white.
  2. Title: light gray is replaced with a medium gray.
  3. Text: medium gray is replaced with a darker gray.
  4. FAB color: light indigo is replaced by a deeper indigo.

Here are the new, improved contrast ratios:

Background

View color

Contrast ratio

Lorem Ipsum Title

#FFFFFF

Light gray (#757575)

4.61:1

Lorem Ipsum Text

#FFFFFF

Dark gray (#424242)

10.05:1

Floating Action Button

#FFFFFF

Indigo (#303F9F)

8.98:1

Accessibility Scanner is a tool that suggests accessibility improvements for Android apps. For I/O attendees, the app is already installed on all kiosk devices.

Accessibility Scanner suggests improvements—such as enlarging small touch targets, increasing contrast, and providing content descriptions—so that individuals with accessibility needs can use your app more easily.

To get started, navigate to Settings > Accessibility. Locate and turn on Accessibility Scanner (Tap "OK" and "Begin Authorization" to complete the setup workflow).

Your screen should now look like this:

You can tap the blue Floating Action Button (FAB) to kick off Accessibility Scanner. We'll be using that in a moment. The FAB can be moved by long-pressing on it.

Return to the accessibility demo app, and launch the Insufficient Contrast Activity. Then, click on the blue Accessibility Scanner FAB.

Accessibility Scanner prepares a set of suggestions for accessibility-related improvements:

Click on a view (highlighted through an orange border) to see Scanner's suggestions. For instance, when you click on the "Lorem Ipsum" title, Scanner suggests changing the text contrast:

Take some time to see what scanner has to say about the other views we discussed in the previous step.

Now, use the Back button to navigate back to the activity. Use the Checkbox to toggle to the "Fix low contrast" version of this activity.

Click on the Activity Scanner FAB to kick off a scan. You notice that Accessibility Scanner no longer suggests increasing contrast.

We've explored only a tiny fraction of the functionality provided by Accessibility Scanner. Experiment using Scanner with other activities in this codelab and with other apps that you use.

Turning off Accessibility Scanner

Navigate to Settings -> Accessibility and set Accessibility Scanner to Off.

We've touched on a lot of topics related to Android accessibility. Here are some links and resources you can explore:

Google I/O 2011: Leveraging Android Accessibility APIs To Create An Accessible Experience

Google I/O 2012 - Making Android Apps Accessible

Google I/O 2013 - Enabling Blind and Low-Vision Accessibility On Android

Google I/O 2015 - Improve your Android app's accessibility