Sample accessibility audit report

eSpeak NG Text-to-Speech for Android, version 2.8.0. Audited October 2026.

This sample is an audit of my own app. eSpeak NG Text-to-Speech is made by RedZoc, my apps brand, and many blind people use it as TalkBack’s voice. I’m publishing it to show what a report looks like, and to hold my own work to the same standard. The fixes will go into a coming update of the app.

Summary

  • Scope: 10 screens of the Android app: home, Settings, Reader, Pronunciation Dictionary, Forum, Ask a question, Discussion, Search, Remove Ads, and About with Contact. Also the voice-data installer and the ads.
  • Method: for this sample, a review of the app’s code against WCAG 2.2 Level AA, with the main finding confirmed with TalkBack on a phone. A client audit tests every finding with TalkBack or VoiceOver on real devices.
  • Result: no critical problems: every key task can be completed with TalkBack. 7 serious, 18 moderate and 15 minor findings.
  • Fix first: the sliders that announce the wrong value or no name, the volume that can silence the phone, the full-screen ads that interrupt TalkBack users, and the voice-data installer’s missing progress.
  • Works well: the Pronunciation Dictionary is a model of accessible design, and text sizes and body contrast are good throughout.

How to read the findings

  • Critical: blocks a key task.
  • Serious: a key task is very hard, or risky.
  • Moderate: a real barrier, with a workaround.
  • Minor: friction or polish.

Code examples are for Android (Java and XML layouts).

Serious findings

Settings sliders announce the wrong value

Where: Settings: the Speech rate, Pitch, Pitch range, Volume and Word gap dialogs

What happens: TalkBack says “175 WPM, seek control, 25 percent”. The value is placed in the slider’s name, and the percentage is the slider’s position, not the rate. The slider has no name of its own, so the user hears two conflicting numbers and no label. Confirmed with TalkBack.

Severity: Serious: changing the speech rate is the main reason to open Settings.

WCAG 2.2: 4.1.2 Name, Role, Value (Level A)

Fix: Name the slider after the setting, and give the value as its state description:

// When the dialog opens
seekBar.setContentDescription(getTitle());

// In onProgressChanged, instead of setContentDescription(text)
ViewCompat.setStateDescription(seekBar, text);

Reader sliders have no name

Where: Reader: the Speed, Pitch and Font size dialogs

What happens: TalkBack says only “seek control, 37 percent”, instead of “Reading speed, 175 words per minute”.

Severity: Serious: speed is a core reading setting.

WCAG 2.2: 4.1.2 Name, Role, Value (Level A)

Fix: Name the slider and keep its spoken value in step with the visible value:

seekBar.setContentDescription(getString(titleRes));
ViewCompat.setStateDescription(seekBar, valueView.getText());

Volume can be set to zero

Where: Settings: Volume

What happens: The volume can be set to 0 percent. When eSpeak NG is TalkBack’s voice, every spoken word on the phone stops, and a blind user can’t undo it without sighted help or a second speech engine.

Severity: Serious: it can lock the user out of the whole phone.

WCAG 2.2: No exact criterion; closest is 3.3.4 Error Prevention

Fix: Don’t allow a volume too low to hear, or confirm very low values with a spoken sample and undo them automatically if not confirmed:

pref.setMin(Math.max(parameter.getMinValue(), 20));

Full-screen ad opens on top of Settings and Forum

Where: Home screen: “Manage eSpeak NG settings” and “Forum”

What happens: A full-screen ad opens on top of the screen the user asked for, and TalkBack focus moves into it. The user believes they are in Settings, but every swipe reads the ad.

Severity: Serious for screen reader users.

WCAG 2.2: No Level AA criterion; 2.2.4 Interruptions and 3.2.5 Change on Request (Level AAA) describe it

Fix: Don’t show full-screen ads at these entry points. If they stay, skip them while TalkBack is on:

AccessibilityManager am = getSystemService(AccessibilityManager.class);
if (am != null && am.isTouchExplorationEnabled()) return;

Ad on return can cover any screen

Where: Whole app, when the user comes back to it

What happens: Returning to the app can show a full-screen ad over whatever screen was open: the voice-data installer opened from Android settings, a half-filled dialog, or the purchase screen after paying with a UPI app.

Severity: Serious: the user lands somewhere other than where they left.

WCAG 2.2: 3.2.1 On Focus (Level A), closest match

Fix: Allow the ad only over the home and Reader screens:

if (!(activity instanceof MainActivity
      || activity instanceof ReaderActivity)) return;

Voice-data installer shows no progress and no result

Where: “Install voice data”, opened from Android’s text-to-speech settings

What happens: The progress bar stays at 0 percent for the whole installation, and the window closes without saying whether it worked. A code bug adds the bytes written to the total instead of to the progress.

Severity: Serious: new users see this screen while setting up the engine.

WCAG 2.2: 4.1.3 Status Messages (Level AA); 4.1.2 Name, Role, Value

Fix: Count the progress correctly and announce the result:

progress.progress += bytesRead;   // was: progress.total += bytesRead

getWindow().getDecorView().announceForAccessibility(
    getString(ok ? R.string.voice_data_installed
                 : R.string.voice_data_failed));

Contact form fails silently

Where: Contact

What happens: After Submit, “Please wait” appears. If the network fails, it closes and nothing is said, so the user can’t tell whether the message was sent.

Severity: Serious: the user may think a support request was sent when it wasn’t.

WCAG 2.2: 3.3.1 Error Identification (Level A); 4.1.3 Status Messages

Fix: When the request fails, show the same message as for a server error:

rest.hideProgress();
new AlertDialog.Builder(this)
    .setMessage(R.string.dialog_contact_failure_message)
    .setPositiveButton(android.R.string.ok, null)
    .show();

Moderate findings

Brand red is just below the contrast minimum

Where: The title bar on every screen, and the Buy, Submit, Post and Send buttons

What happens: White text on the app’s red (#F20404) has a contrast of 4.38:1. Normal-size text needs 4.5:1, so low-vision users get text just below the minimum.

Severity: Moderate.

WCAG 2.2: 1.4.3 Contrast (Minimum) (Level AA)

Fix: Use the app’s existing darker red, #D90303 (5.3:1):

<color name="colorPrimary">#D90303</color>

Reader doesn’t say when playback starts or stops

Where: Reader

What happens: Pressing Play disables the Play button while TalkBack focus is still on it, and nothing says “Playing”, “Paused” or “Finished”. The user has to explore the screen to find out what happened.

Severity: Moderate.

WCAG 2.2: 4.1.3 Status Messages (Level AA)

Fix: Add a status line that TalkBack reads automatically:

<TextView android:id="@+id/reader_status"
    android:accessibilityLiveRegion="polite" ... />

status.setText(speaking ? R.string.reader_playing
                        : R.string.reader_paused);

Voice variant dropdowns have no name

Where: Settings: Voice variant

What happens: The labels “Category” and “Variant” are separate text, not linked to their dropdowns. TalkBack says only “Male, drop-down list”.

Severity: Moderate.

WCAG 2.2: 1.3.1 Info and Relationships (Level A); 4.1.2 Name, Role, Value

Fix: Link each label to its dropdown:

<TextView android:labelFor="@+id/category"
    android:text="@string/category" ... />

Two punctuation options can’t be reached by keyboard

Where: Settings: Speak punctuation

What happens: “All” and “Custom” are marked as not focusable, so someone using a keyboard, switch access or a D-pad can only choose “None”.

Severity: Moderate.

WCAG 2.2: 2.1.1 Keyboard (Level A)

Fix: Remove these attributes from the radio buttons and their group:

android:focusable="false"
android:focusableInTouchMode="false"

Ad in the forum list isn’t labelled

Where: Forum, after the fifth question

What happens: A large ad sits in the list of questions with nothing saying it is an ad, so its content sounds like another post.

Severity: Moderate.

WCAG 2.2: 1.3.1 Info and Relationships (Level A)

Fix: Put a visible “Advertisement” label above the ad slot:

<TextView android:text="@string/label_advertisement" ... />
<FrameLayout android:id="@+id/inline_ad_container" ... />

Each forum reply takes four to six swipes

Where: Forum discussion

What happens: Each reply is read as separate pieces: badge, name, time, text, and two buttons. It is easy to lose which button belongs to which reply.

Severity: Moderate.

WCAG 2.2: 1.3.1 Info and Relationships (Level A)

Fix: Read the reply text as one item, and keep the buttons separate:

holder.content.setContentDescription(
    name + ", " + time + ". " + reply);
ViewCompat.setScreenReaderFocusable(holder.content, true);

Other moderate findings:

  • Forum and discussion lists don’t announce loading, empty results or a changed filter (4.1.3).
  • “Ask a question” field is announced as “Type here”, not linked to its visible label (1.3.1, 3.3.2).
  • Contact fields are labelled only by placeholders (“star Name”), and errors appear only as short pop-up messages (3.3.2, 3.3.1).
  • Text fields have no visible edge: white boxes on a white screen (1.4.11).
  • Placeholder text contrast is too low in the Reader (3.5:1) and the punctuation field (1.4.3).
  • Fixed heights cut off text at large font sizes in Contact, Reply and two Settings dialogs (1.4.4).
  • Remove Ads: the price arriving and “You are a Pro user” aren’t announced, and the Buy button disappears under focus (4.1.3, 2.4.3).
  • Donate: no loading, empty or error state, and the button text is always in English (4.1.3).
  • Import dictionary: an unlabelled, usually empty dropdown, with no success or failure message (4.1.3, 3.3.1).
  • Reader notification buttons have no icons, and headset buttons don’t control reading (1.1.1).
  • Ads come before the content on the home and Settings screens, and a fixed ad leaves little room at large font sizes (2.4.3, 1.4.10).
  • Every screen is locked to portrait (1.3.4).

Minor findings

  • A setting at its minimum (for example Word gap at 0) opens with no value shown.
  • Empty-field errors in Ask and Reply are short pop-up messages, not attached to the field.
  • The best answer reads the author’s name straight after the answer, with nothing in between.
  • Forum list items read four pieces of information before the question itself.
  • No headings on the home, Remove Ads and About screens.
  • “App Logo” labels add nothing; About reads the version before the app name.
  • About’s social icons aren’t announced as buttons, and fail silently without a browser.
  • Some confirmation dialogs have no title; “Please wait” can’t be cancelled.
  • Saving audio shows progress that can’t be cancelled and isn’t announced.
  • Reader menu items silently do nothing if the speech engine isn’t ready.
  • Reader voice names end with a code that is spelled out letter by letter.
  • Dictionary search doesn’t say how many words match.
  • Home screen rows aren’t announced as buttons; profile photos aren’t explicitly hidden.

What works well

  • The Pronunciation Dictionary is a model screen: real labels, errors announced on the field, edit and delete reachable with a single tap (nothing hidden behind long-press), delete confirmed, and an empty state that TalkBack reads.
  • Home screen rows each read as one item, and dividers are hidden from TalkBack.
  • All text sizes scale with the system font size, and body text contrast is 15:1.
  • The forum has headings, a labelled language filter, and named “More options” buttons on each reply; search announces how many results it found.
  • Times are read naturally (“5 minutes ago”), and Settings examples are written in words because TalkBack reads them through eSpeak itself.
  • Ads are never shown to Pro users, need consent, and the ad on return is limited to once every four hours.

Want one for your product?

See what an audit includes and what it costs, or email hello@rameesmuhammed.com.