使用无障碍服务完成一键拨打微信视频电话
无障碍服务适配大家应该多多少少的都遇到过,简单点讲就是给图片、文本等控件加上 android:contentDescription=""
标签,这样在使用无障碍服务(比如手机自带的 talkback)时,可以将contentDescription
的内容以声音的方式读出来,方便视障用户使用我们的 app。
这不是本文的重点,重点是在无障碍–>已安装的服务中中发现了一些其他的应用也提供了一些无障碍服务,比如某输入法提供了”智能回复”、”智能应答”等服务,某些应用还提供了类似于一键进行微信视频通话功能,这玩意咋搞的?我们能不能搞?能不能给老人做一个简单的工具,去掉那些花里胡哨的功能,点个按钮就能和我们进行视频通话?
不要用无障碍服务做违法的事情!!!不要用无障碍服务做违法的事情!!!不要用无障碍服务做违法的事情!!!
查了一些资料,我们可以使用AccessibilityService
来实现该功能。该服务可以在页面切换或者发生其他变化时回调某些方法,我们可以根据这些回调,获取到页面的节点(控件)信息,来进行点击、长按等操作。
第一步:创建与配置
我们需要自定义一个继承自AccessibilityService
的 service,然后在AndroidManifest.xml
文件中注册一下,就想普通的 service 差不多,这里有三个可以被重写方法
1 |
|
在清单文件中注册一下
1 |
|
需要注意这里的meta-data
标签,其中的name
属性值是固定的,resource
属性则是我们为无障碍服务提供的配置文件。大致有这么一些属性accessibility_service_config.xml
是一个用于配置 AccessibilityService
的 XML 文件,其中包含了许多属性,用于定义服务的行为和特性。以下是这些属性的详细介绍和示例说明:
1. android:description
描述服务的用途,通常是一个字符串资源的引用。这个值会展示在开启无障碍服务时的帮助说明中
2. android:accessibilityEventTypes
定义服务要监听的事件类型。可以是以下之一或多个的组合:
typeAllMask
typeViewClicked
typeViewFocused
typeViewLongClicked
typeViewSelected
typeViewTextChanged
typeWindowContentChanged
typeWindowStateChanged
。
这里我们只需要监听 typeWindowContentChanged
和 typeWindowStateChanged
就足够了
3. android:packageNames
指定服务要监听的应用包名。多个包名可以用逗号分隔。这个没啥好说的
4. android:accessibilityFeedbackType
定义服务的反馈类型,就是如何给用户反馈,可以是以下之一或多个的组合:
feedbackSpoken
: 适用于需要将信息通过语音读出来的情况,例如屏幕阅读器。feedbackHaptic
: 适用于需要通过振动提醒用户的情况,例如通知用户某个操作成功或失败。feedbackAudible
: 适用于需要通过音效提醒用户的情况,例如提示音。feedbackVisual
: 适用于需要通过视觉效果(如闪烁、颜色变化)提醒用户的情况。feedbackGeneric
: 适用于不特定于某一种反馈类型的情况。feedbackBraille
: 适用于需要将信息传递给盲文设备用户的情况。
5. android:notificationTimeout
定义服务在处理连续事件之间的最短时间间隔,以毫秒为单位。当辅助功能服务接收到大量的连续事件时,可能会导致性能问题或用户体验不佳。通过设置 notificationTimeout
,可以指定一个时间窗口,在这个时间窗口内重复的事件将被合并为一个事件,从而减少处理的频率。
6. android:canRetrieveWindowContent
定义服务是否可以检索窗口内容。设置为 true
表示服务可以访问窗口内容。
7. android:settingsActivity
指定一个设置活动的类名,用户可以通过辅助功能设置页面进入该活动。配置了该属性之后,用户可以在开启无障碍服务页面点击更多设置直接进入到该页面
8. android:canRequestTouchExplorationMode
属性用于指定辅助功能服务是否可以请求触摸探索模式。触摸探索模式是一种特殊的输入模式,通常用于帮助视力障碍用户使用触摸屏设备
这里也需要我们在代码中设置一下
1 |
|
并且,在处理事件中我们也需要处理更多的事件
启用时:应用需要处理更多的辅助功能事件,如 TYPE_TOUCH_EXPLORATION_GESTURE_START 和 TYPE_TOUCH_EXPLORATION_GESTURE_END。这些事件帮助应用确定用户正在进行触摸探索。
未启用时:应用只需处理标准的触摸事件。
9. android:canRequestEnhancedWebAccessibility
定义服务是否可以请求增强的网页辅助功能。
10. android:canRequestFilterKeyEvents
用于指定辅助功能服务是否可以请求过滤键事件(key events)。这对于开发辅助功能服务(如屏幕阅读器或其他辅助工具)非常重要,因为它允许这些服务拦截和处理按键事件,以提供更好的用户体验和辅助功能支持。同样的,不仅要在配置文件中声明,也需要在代码中设置
1 |
|
11. android:canPerformGestures
定义服务是否可以执行手势。如果为 true,我们可以这样执行手势
1 |
|
12. android:accessibilityFlags
定义服务的辅助功能标志,这些标志定义了服务的行为和特性。通过设置不同的标志,开发者可以控制辅助功能服务如何与系统和应用交互。可以是以下之一或多个的组合:
**
flagIncludeNotImportantViews
**:- 作用:包括那些通常被认为不重要的视图(如布局视图)在辅助功能事件中。
- 使用场景:当需要确保所有视图都被辅助功能服务处理时使用。
**
flagRequestTouchExplorationMode
**:- 作用:请求触摸探索模式,这对于视力障碍用户非常有用。
- 使用场景:当辅助功能服务需要解释触摸事件并提供反馈时使用。
**
flagReportViewIds
**:- 作用:报告视图的资源 ID。
- 使用场景:当辅助功能服务需要识别和操作特定视图时使用。
**
flagRetrieveInteractiveWindows
**:- 作用:允许辅助功能服务检索交互窗口。
- 使用场景:当需要处理多个窗口或弹出窗口时使用。
当然我们也可以在代码中设置标志
在你的 AccessibilityService
中,你可以使用 AccessibilityServiceInfo
来设置标志:
1 |
|
示例完整配置文件
下面是我们这次需要用到的配置文件内容:
1 |
|
通过配置这些属性,你可以精确地控制 AccessibilityService
的行为,以满足特定的需求和用例。
第二步:rua代码
在上面我们已经做好了基础配置,下面开始rua 代码,看看我们应该怎么做。
分析路径流程
我们先做好微信的前期准备工作:通话双方是好友、微信已经登录。
那么我们的使用流程大致时这样的:
打开微信
点击底部通讯录
找到这个好友(可能需要滑动通讯录列表)点击一下进入到好友信息页面
点击信息页面的音视频通话
在底部弹窗中点击视频通话或者语音通话
简单的 API 调用准备
onAccessibilityEvent
当触发了我们在配置文件中指定的事件时,系统会回调AccessibilityService#onAccessibilityEvent(event: AccessibilityEvent)
这个方法。
我们可以通过event
对象获取触发这个事件的包名,触发的事件类型等,
getRootInActiveWindow
我们可以在AccessibilityService
中调用这个方法获取当前页面的根节点,这个节点可以看做是当前视图树的根节点,这样我们就可以遍历整个视图树了。
同样的,我们也可以通过AccessibilityNodeInfo
实例来获取对应节点的属性,比如是否可以点击(isClickable)、类型(className)、按钮|文本内容(text)、无障碍服务标签内容(contentDescription)等。我们可以根据这些属性来判断是不是我们需要的节点(控件)
注意事项
就像我们平时开发一样,有些事件并不是直接设置在 TextView 或者 Button 上的,可能是设置在它们的父级组件上,比如LinearLayout或者RelativeLayout等。所以当我们获取到对应的节点后,需要判断一下是不是我们需要的节点,如果不是的话,就在找找父级是不是我们需要的节点。
当然如果我们知道某个页面某个节点的id,就不需要这么麻烦了,直接根据 id 查找就好了。
权限
需要开启无障碍服务才可以进行对应的操作
1 |
|
1 |
|
动手开工
打开微信
这个很简单哇,知道微信的包名就好了
1 |
|
当我们打开微信后,标记接下来需要点击通讯录按钮:current_step= click_contacts;
点击底部通讯录
这个就需要用到上面准备好的AccessibilityService
了,按照上面的配置,当我们打开微信之后,就开始回调onAccessibilityEvent(event: AccessibilityEvent)
这个方法了。
我们假设用户使用的是中文,我们需要找到”通讯录”这个按钮对应的AccessibilityNodeInfo实例,然后调用performAction(AccessibilityNodeInfo.ACTION_CLICK)
进行点击就好了。
注意,这个的通讯录
文本并不是可以点击的,我们打开无障碍服务talkback
将框框移动到通讯录这里,就可以看到通讯录
和上面的图标是一体的。但我们也不清楚他们到底是怎么实现的,所以我们查找这个文本的父级控件,看是否能点,不能点击就再往上查找。多次尝试之后,发现需要向上查找两次。这里写了一个扩展方法
1 |
|
这里的参数parentCount
表示需要向上查找几次。
我们点击通讯录的时候调用rootInActiveWindow.clickNodeByText(arrayOf("通讯录"), 2)
就可以了.
点击成功后,我们标记接下来需要点击联系人:current_step=click_contact;
找到好友
通讯录是个列表,我们猜要不是 ListView,要不是 RecyclerView,我觉得不大可能是 ScrollView。要注意。右侧还有一个字母列表,不要搞错了。
我们先从当前可看到的页面查找联系人。
这里也搞了个扩展方法
1 |
|
查找这个联系人
1 |
|
如果contactNode
为空,表示当前可视内容中没有这个联系人,我们需要滑动列表。
首先,找到联系人列表的RecyclerView
,别问为啥是RecyclerView
,试了好多次试出来的。
1 |
|
找到列表控件后滑动一下
1 |
|
注意,这里列表的滑动同样会触发onAccessibilityEvent
这个方法,我们再重复上面的流程,直到找到这个联系人控件。需要注意的是,这里的联系人显示的名字要是单个英文字母,这会和列表分组上面的单个英文字母相同,导致查找到的控件不是我们想要的
当我们找到这个联系人控件后,进行点击
1 |
|
点击音视频通话
这个就比较简单了,还是调用我们上面写的扩展方法找到按钮,然后点击就行了
1 |
|
点击弹窗中的视频通话
这个就更简单了
1 |
|
一个语音通话,一个视频通话。
到这里我们就可以进行视频通话了。
总结
上面的流程是最理想的状态,还有一些奇奇怪怪的问题:
比如我们视频通话结束后,需要返回到列表页,也就是在通话结束后点击左上角的返回,这个功能没有写。
比如打开微信的时候不是在首页,比如在浏览公众号信息怎么办?同样需要找到左上角的返回按钮,一直到首页之后才可以进行点击通讯录的操作。
比如联系人的名字就是单个英文字母,上面也提到,这种情况下查找到的会是分组的名称,无法进行点击。
或者我们可以从首页点击右上角的搜索,输入联系人名字,然后在搜索列表中点击联系人,进入到聊天页面,然后点击左下角加号,在更多菜单里面点击音视频通话也行。
放个最终效果的视频吧