45

I'm developing an application that uses TalkBack to guide people through it. However, in those situations I want to have some subtile differences in the layout of the application so navigation is easier and also have extra voice outputs (with TextToSpeech) to help guide the user.

My problem is that I only want those changes and extra outputs if the user has TalkBack active.

Is there any way to know if it is? I didn't find anything specific to access TalkBack settings directly, but I was hoping there was some form of accessing general phone settings that could let me know what I need.

Francesco - FL
  • 603
  • 1
  • 4
  • 25
David Carvalho
  • 549
  • 1
  • 5
  • 8

9 Answers9

89

The recommended way of doing this is to query the AccessibilityManager for the enabled state of accessibility services.

AccessibilityManager am = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
boolean isAccessibilityEnabled = am.isEnabled();
boolean isExploreByTouchEnabled = am.isTouchExplorationEnabled();
caseyburkhardt
  • 1,145
  • 7
  • 9
  • 12
    If the TalkBack service is suspended (using the gesture shortcuts), the AccessibilityManager will not change its enabled state to `disabled`. Very very small edge case, I suppose, and technically it _is_ still enabled, but this tripped me up today. – ataulm Jan 11 '15 at 21:39
  • If you use the TalkBack service on the Nexus Player, does `isTouchExplorationEnabled()` still return true? – ataulm Sep 21 '15 at 17:19
  • 2
    To answer my own question, `touch exploration` is available on Nexus Player (on Lollipop at least) and it will return true if this is checked. – ataulm Oct 13 '15 at 10:58
  • 11
    More details: if you are strictly interested in whether **TalkBack** is enabled, use `am.isTouchExplorationEnabled()`. (If Select to Speak is enabled and TalkBack disabled, `am.isEnabled()` will still return true.) – Jonik Oct 24 '17 at 10:53
9

Novoda have released a library called accessibilitools which does this check. It queries the accessibility manager to check if there are any accessibility services enabled that support the "spoken feedback" flag.

AccessibilityServices services = AccessibilityServices.newInstance(context);
services.isSpokenFeedbackEnabled();

public boolean isSpokenFeedbackEnabled() {
    List<AccessibilityServiceInfo> enabledServices = getEnabledServicesFor(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
    return !enabledServices.isEmpty();
}

private List<AccessibilityServiceInfo> getEnabledServicesFor(int feedbackTypeFlags) {
    return accessibilityManager.getEnabledAccessibilityServiceList(feedbackTypeFlags);
}
ataulm
  • 15,195
  • 7
  • 50
  • 92
  • 1
    Exploring the git hub one can see lots of good lines of Android API. Particularly detecting if a voice over service is enabled using 2 lines of code was my pick. Neat and nice. – Nar Gar May 24 '16 at 03:01
  • Interesting, but that repo was archived 5 days ago. – rekire Feb 15 '22 at 06:22
  • Added code from the repo. It was archived because no one's around to maintain it anymore I suppose. – ataulm Feb 22 '22 at 20:26
8

You can create an inline function in kotlin like:

fun Context.isScreenReaderOn():Boolean{
    val am = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
    if (am != null && am.isEnabled) {
        val serviceInfoList =
            am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN)
        if (!serviceInfoList.isEmpty())
            return true
    }
    return false}

And then you can just call it whenever you need it like:

if(context.isScreenReaderOn()){
...
}

Tested and works fine for now.

  • Even more kotlin to have a val instead of a function `val Context.isScreenReaderOn: Boolean get() { val am = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager if (am.isEnabled) { val serviceInfoList = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN) if (serviceInfoList.isNotEmpty()) return true } return false }` – latsson Nov 05 '20 at 11:39
  • This will still return the previous value if you return to your app after disabling/enabling talkback or screen reader from Settings app... – Alvin Dizon Mar 15 '21 at 03:10
6

For an example, look at isScreenReaderActive() in HomeLauncher.java file in the Eyes-Free shell application (via groups thread).

To sum up: you detect all screen readers with Intents, then query the status provider of each to see if it is active.

If you really want to limit it to TalkBack only, you could try checking the ResolveInfo.serviceInfo.packageName for each result returned from queryIntentServices() to see if it matches the TalkBack package.

Mike
  • 2,422
  • 23
  • 14
  • I ended up going through all the services and see if the TalkBack one was active, but your solution is much better, thanks. I did had a problem with a cracked ROM, though. No services were being returned. – David Carvalho Feb 28 '11 at 11:39
  • Mike, I have tried this in Jelly Bean and it doesn't seem to work... the cursor seems to be empty. DO you have any idea on how to do this in Jelly Bean? I created a new question: http://stackoverflow.com/questions/11831666/how-to-check-if-talkback-is-active-in-jellybean – pandre Aug 06 '12 at 16:33
3
    AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
    if (am != null && am.isEnabled()) {
        List<AccessibilityServiceInfo> serviceInfoList = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
        if (!serviceInfoList.isEmpty())
            return true;
    }
    return false;
alexbtr
  • 3,292
  • 2
  • 13
  • 25
2

For me, I solved this problem in this way , it works well in my project:

  1. use getEnabledAccessibilityServiceList() to get all Accessibility service, a service whose status is open will be in this list
  2. Talkback contain a activity named com.android.talkback.TalkBackPreferencesActivity, you can traversing the list to find whether the talkback service is open

The detailed code below:

    private static final String TALKBACK_SETTING_ACTIVITY_NAME = "com.android.talkback.TalkBackPreferencesActivity";

    public static boolean accessibilityEnable(Context context) {
        boolean enable = false;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            try {
                AccessibilityManager manager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
                List<AccessibilityServiceInfo> serviceList = manager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
                for (AccessibilityServiceInfo serviceInfo : serviceList) {
                    String name = serviceInfo.getSettingsActivityName();
                    if (!TextUtils.isEmpty(name) && name.equals(TALKBACK_SETTING_ACTIVITY_NAME)) {
                        enable = true;
                    }
                }
            } catch (Exception e) {
                if (Logging.isDebugLogging()) {
                    e.printStackTrace();
                }
            }
        }
        return enable;
}
David Z
  • 21
  • 1
  • They could change the name of the settings activity in future updates. – ataulm Jan 15 '17 at 20:34
  • But you can check the Android Accessibility Suite package name with `accessibilityServiceInfo.getResolveInfo().serviceInfo.processName` instead of its name – Jaco Nov 19 '20 at 17:28
2

Thanks to @david-z answer (https://stackoverflow.com/a/41357058/2713403) I made this approach to know if the Android Accessibility Suite by Google is enabled

/**
 * This method checks if Google Talkback is enabled by using the [accessibilityManager]
 */
private fun isGoogleTalkbackActive(accessibilityManager : AccessibilityManager) : Boolean
{
    val accessibilityServiceInfoList = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN)
    for (accessibilityServiceInfo in accessibilityServiceInfoList)
    {
        if ("com.google.android.marvin.talkback".equals(accessibilityServiceInfo.resolveInfo.serviceInfo.processName))
        {
            return true
        }
    }
    return false
}

Remember to register the google URI as constant :) and get the Accessibility Manager instance as @caseyburkhardt says (https://stackoverflow.com/a/12362545/2713403). The difference with the @david-z answer is that I got the Android Accessibility Suite package name instead of its app name because it is more secure. If you want to review if another accessibility suite is enabled (like the Samsung Screen Reader) after this check, you can check if (accessibilityManager.isTouchExplorationEnabled)

Cheers!

Jaco
  • 923
  • 2
  • 14
  • 28
2

If you are working with compose you can add this utility extension on Context:

@Composable
internal fun Context.collectIsTalkbackEnabledAsState(): State<Boolean> {
    val accessibilityManager =
        this.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager?

    fun isTalkbackEnabled(): Boolean {
        val accessibilityServiceInfoList =
            accessibilityManager?.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN)
        return accessibilityServiceInfoList?.any {
            it.resolveInfo.serviceInfo.processName.equals(TALKBACK_PACKAGE_NAME)
        } ?: false
    }

    val talkbackEnabled = remember { mutableStateOf(isTalkbackEnabled()) }
    val accessibilityManagerEnabled = accessibilityManager?.isEnabled ?: false
    var accessibilityEnabled by remember { mutableStateOf(accessibilityManagerEnabled) }

    accessibilityManager?.addAccessibilityStateChangeListener { accessibilityEnabled = it }

    LaunchedEffect(accessibilityEnabled) {
        talkbackEnabled.value = if (accessibilityEnabled) isTalkbackEnabled() else false
    }
    return talkbackEnabled
}

private const val TALKBACK_PACKAGE_NAME = "com.google.android.marvin.talkback"

And then use it in the target composable:

@Composable
fun SomeComposable() {
    val talkbackEnabled by LocalContext.current.collectIsTalkbackEnabledAsState()

    if (talkbackEnabled) {
        /** do something here **/
    }
}
Utkarsh Tiwari
  • 125
  • 2
  • 8
-5

Open system setting and go to accessibility and tap to off Talk back option