如何确定 Android TalkBack 是否已激活?

46

我正在开发一个使用TalkBack引导用户的应用程序。但是,在这种情况下,我希望在应用程序布局中有一些细微的差异,以便更容易进行导航,并且具有额外的语音输出(使用TextToSpeech)来帮助指导用户。

我的问题是,仅当用户激活TalkBack时,才想要这些更改和额外的输出。

有没有办法知道是否已经激活了TalkBack?我没有找到直接访问TalkBack设置的特定信息,但我希望能够访问通用手机设置,以便了解我所需要的内容。

9个回答

90

推荐的方法是查询AccessibilityManager以获取辅助功能服务的启用状态。

AccessibilityManager am = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
boolean isAccessibilityEnabled = am.isEnabled();
boolean isExploreByTouchEnabled = am.isTouchExplorationEnabled();

12
如果暂停TalkBack服务(使用手势快捷方式),AccessibilityManager不会更改其启用状态为“已禁用”。我想这是一个非常小的特例情况,从技术上讲它仍然是启用的,但今天这让我有些困扰。 - ataulm
如果您在Nexus Player上使用TalkBack服务,isTouchExplorationEnabled()是否仍然返回true? - ataulm
2
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - ataulm
11
更多细节:如果严格来说您只是想知道 TalkBack 是否已启用,请使用 am.isTouchExplorationEnabled()。(如果“选择以发言”已启用但未启用TalkBack,am.isEnabled() 仍将返回true。) - Jonik

11

您可以像这样在Kotlin中创建内联函数:

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}

然后您只需在需要时调用它,如下所示:

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

目前经过测试并且运作良好。


甚至更多的 Kotlin 代码使用 val 替代函数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
如果你在设置应用中禁用/启用Talkback或屏幕阅读器后返回到你的应用程序,它仍然会返回先前的值... - Alvin Dizon

9
Novoda发布了一个名为accessibilitools的库,它可以进行此检查。它查询辅助功能管理器以检查是否启用了支持“语音反馈”标志的任何辅助功能服务。
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);
}

1
浏览 GitHub,可以看到许多优秀的 Android API 代码行。特别是使用两行代码检测语音服务是否启用,这是我的选择。整洁而美好。 - Nar Gar
有趣,但那个仓库已经在5天前被存档了。 - rekire
从代码库中添加了代码。我想它被归档了,因为没有人维护它了。 - ataulm

6

举个例子,看一下Eyes-Free shell应用程序中HomeLauncher.java文件中的isScreenReaderActive()(通过groups thread)。

总之:您可以使用Intents检测所有屏幕阅读器,然后查询每个提供程序的状态以查看其是否处于活动状态。

如果您真的想将其限制为仅TalkBack,则可以尝试检查queryIntentServices()返回的每个结果的ResolveInfo.serviceInfo.packageName,以查看它是否与TalkBack包匹配。


我最终检查了所有的服务以查看TalkBack是否处于激活状态,但你的解决方案更好,谢谢。不过我的ROM已经被破解,出现了问题,没有返回任何服务。 - David Carvalho
Mike,我已经在Jelly Bean上尝试过了,但好像不起作用...光标似乎是空的。你有任何关于如何在Jelly Bean中实现这一点的想法吗?我创建了一个新的问题:https://dev59.com/1GbWa4cB1Zd3GeqPcfYO - pandre

3

如果您正在使用Compose进行编程,可以在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"

然后在目标组合中使用它:

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

    if (talkbackEnabled) {
        /** do something here **/
    }
}

addAccessibilityStateChangeListener在我切换它的开关时似乎无法接收到事件。 - undefined

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;

2

对于我来说,我是通过以下方式解决了这个问题,在我的项目中它很好用:

  1. 使用getEnabledAccessibilityServiceList()获取所有辅助功能服务,其中状态为开启的服务将在此列表中
  2. Talkback包含一个名为com.android.talkback.TalkBackPreferencesActivity的活动,您可以遍历列表以查找Talkback服务是否已开启

下面是详细代码:

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

在未来的更新中,他们可能会更改设置活动的名称。 - ataulm
但是你可以通过 accessibilityServiceInfo.getResolveInfo().serviceInfo.processName 检查 Android 辅助功能套件的包名,而不是它的名称。 - Jaco

2
感谢@david-z的答案(https://dev59.com/tm435IYBdhLWcg3w9FBY#41357058),我采用了这种方法来确定Google的Android辅助功能套件是否已启用。
/**
 * 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
}

记得将Google URI注册为常量,并像@caseyburkhardt所说获取Accessibility Manager实例(https://dev59.com/tm435IYBdhLWcg3w9FBY#12362545)。与@david-z的答案不同之处在于,我获取了Android Accessibility Suite包名而不是其应用程序名称,因为这样更加安全。如果您想在此检查后查看是否启用了其他辅助功能套件(例如三星屏幕阅读器),可以检查if (accessibilityManager.isTouchExplorationEnabled)

干杯!


-5

打开系统设置,进入辅助功能,关闭Talk back选项


4
我认为OP是在询问如何通过编程检查 TalkBack 是否处于激活状态。 - adiga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接