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. If you run into any issues (code bugs, grammatical errors, unclear wording, etc.) as you work through this codelab, please report the issue via the Report a mistake link in the lower left corner of the codelab.
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:
In this codelab, you will:
The codelab is structured in small, discrete steps, and each step focuses on a specific accessibility issue.
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:
Using a terminal, change into the root directory.
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 a screen 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 screen or feature accessible.
Launch Android Studio by clicking the Studio icon:
Select the Open an existing Android Studio project option:
Navigate to the location where you cloned or unzipped the source, and select basic-android-accessibility-Java. If you prefer Kotlin, select basic-android-accessibility-Kotlin.
Your Android studio screen should now look something like this:
Expose the directory structure of the application using the following steps:
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:
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 something like this:
Don't close the app. We'll return to it soon.
In this step, we'll be setting up TalkBack, which is included in the Android Accessibility Suite App (usually included by default in Android).
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.
We'll use TalkBack with 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 Pixel device; the location of TalkBack settings can vary on other Android devices.
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.
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:
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:
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 Get Started on Android with 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 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.
Navigate to Content Labeling Screen (remember this is a two-step process when using TalkBack). The screen looks like this:
The screen contains a title, an EditText for entering text, a "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:
"Enter favorite song"
in the layout XML. Let's improve the experience for the TalkBack user by properly labeling content.
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 Android Studio's lint check by confirming that you are not providing a contentDescription for this view intentionally, rather than forgetting to do so.
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_alignParentStart="true"
android:contentDescription="@string/share"
android:background="@null"
android:src="@drawable/ic_share" />
Press the green Play icon from the menu bar to run the app.
Tap on the Share icon. Talkback now announces, "Share Button".
The Play/Pause toggle is managed dynamically through the code. Look at the code in the ContentLabelingFragment class:
private void updateImageButton() {
if (mPlaying) {
mPlayPauseToggleImageView.setImageResource(R.drawable.ic_pause);
} else {
mPlayPauseToggleImageView.setImageResource(R.drawable.ic_play_arrow);
}
}
We toggle the image, but we don't add a contentDescription to describe the current state of the view. Add calls to setContentDescription() so the code 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:
To help users who interact with your app using a keyboard, you may need to manually specify that some clickable items (e.g. ImageView elements) are focusable using android:focusable="true". By default, content is not focusable, so 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.
To see an example for this, navigate to the Content Grouping Screen.
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:
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.
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. TextView elements) 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 elements that we wish to consolidate.
Add the focusable attribute to the <RelativeLayout> element:
<RelativeLayout
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 the Content Grouping Screen 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").
In the example we're considering, having a single TalkBack announcement is better than having six; but this solution has its limits:
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" TextView elements 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, you'll need to split your RelativeLayout element into a new one for each row so that it's structured 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>
Press the green Play icon from the menu bar to run your app and try out Content Grouping Screen once again with TalkBack. The song details are now announced like this:
So far, all the examples have involved using Talkback with views that a user has explicitly focused on. However, you sometimes 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 the Live Region Screen.
This screen shows a multiple choice question. Here's how Talkback works with radio buttons:
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. However, TalkBack doesn't automatically announce this feedback. To instruct TalkBack to do so, we need 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"
android:background="@null"
android:textColor="@color/white"
android:padding="@dimen/standard_padding"
android:textSize="@dimen/large_text" />
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:background="@null"
android:textColor="@color/white"
android:padding="@dimen/standard_padding"
android:textSize="@dimen/large_text"
android:accessibilityLiveRegion="polite" />
Press the green Play icon from the menu bar to run the app. Try using LiveRegionScreen 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 or not ("Incorrect. Checked", or "Correct! Checked", for example).
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.
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 to 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.
You will notice that the solutions to accessibility problems discussed in this codelab so far - adding contentDescription attributes and grouping related UI elements in the same container - have not required you to define the announcements that an AccessibilityService makes. 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:
In every example so far, we've used TalkBack to discuss accessibility features for visually impaired and blind users. We'll switch gears now and talk about accessibility more broadly. We won't be using TalkBack any more in this codelab, so we should disable the tool. First, we need to find our way back to the Accessibility Settings screen. Follow these steps:
To turn off TalkBack, follow these steps:
Many people have difficulty focusing on small touch targets on the screen. This could be because their fingers are large, or because 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 Screen. This screen 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.
Enable developer options by going to Settings > System > 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 the Expand Touch Area Screen, 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 developer options (Settings > Developer Options). In the 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 ratios.
In this step, you'll see two versions of a screen. 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 Screen, 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:
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 |
We've touched on a lot of topics related to Android accessibility. Here are some links and resources you can explore: