简体   繁体   中英

How to know if Android TalkBack is active?

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.

Regards and thanks in advance.

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();

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();

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.

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.

    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;

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;
}

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!

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 **/
    }
}

打开系统设置并转到辅助功能并点击关闭对话选项

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM