AccessibilityService实现行为捕获
语音:Kotlin 版本:1.2.41
最近接到一个很奇葩的需求:捕获系统语音通话聊天(包括微信、电话、QQ),把通话内容记录成音频文件上传到服务端。
接到需求的第一时间当然是度娘谷歌一阵,一顿乱搜之后发现并没有太多能够用来参考的文献。
没有现成的轮子可用,只能撸起袖子,自己动手丰衣足食。
AccessibilityService简介
AccessibilityService中文翻译为辅助服务,它能够帮助我们获取第三方应用的操作信息。
AccessibilityService的所带来的安全隐患在这里不多赘述,主要介绍一些关于它的特性和用法。
本文章将用大家常用的微信来举例,获取微信语音通话界面的控件布局,提取到关键常量之后,用AccessibilityService来捕获。
先来看一段官方对AccessibilityService的描述:
Accessibility services should only be used to assist users with disabilities in using Android devices and apps. They run in the background and receive callbacks by the system when AccessibilityEvents are fired. Such events denote some state transition in the user interface, for example, the focus has changed, a button has been clicked, etc. Such a service can optionally request the capability for querying the content of the active window. Development of an accessibility service requires extending this class and implementing its abstract methods.
Lifecycle
The lifecycle of an accessibility service is managed exclusively by the system and follows the established service life cycle. Starting an accessibility service is triggered exclusively by the user explicitly turning the service on in device settings. After the system binds to a service, it calls onServiceConnected(). This method can be overridden by clients that want to perform post binding setup.
An accessibility service stops either when the user turns it off in device settings or when it calls disableSelf().
从描述中我们可以得到如下几个信息:
1.AccessibilityService只能用于帮助残障人士使用Android设备和应用;
2.运行在后台,可以实现无感知运作;
3.能够监测焦点的切换、点击等事件;
4.APP只能对其进行注册,无法自动开启;
5.启动AccessibilityService需要用户进入“设置-无障碍”手动开启,关闭亦是如此。
放上官方文档的链接(需爬梯):
https://developer.android.com/reference/android/accessibilityservice/AccessibilityService
AccessibilityService基本配置方式
创建MyAccessibilityService继承AccessibilityService
覆写onServiceConnected、onAccessibilityEvent、onInterrupt方法。
三个方法分别为AccessibilityService的三个生命周期。
可以大致理解为“服务启动时”、“服务监听中”、“断开服务”
/*
* Create by parker
* Author: parker
* Create: 2018/12/7 10:48 AM
*
*/
class MyAccessibilityService : AccessibilityService() {
override fun onServiceConnected() {
super.onServiceConnected()
}
override fun onAccessibilityEvent(event: AccessibilityEvent) {
}
override fun onInterrupt() {
}
}
在AndroidManifest.xml中注册:
<service
android:name=".MyAccessibilityService"
android:enabled="true"
android:exported="false"
android:label="@string/my_as"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:process=":BackgroundService">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_config" />
</service>
在res下新建accessibility_config.xml文件:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:canRetrieveWindowContent="true"
android:notificationTimeout="100"
android:description="@string/description"
android:packageNames="com.tencent.mm" />
属性作用如下:
android:description :辅助功能描述,描述该辅助功能用来干嘛的
android:packageNames :指定辅助功能监听的包名,不指定表示监听所有应用
android:accessibilityEventTypes:辅助功能处理事件类型,一般配置为typeAllMask表示接收所有事件
android:accessibilityFlags:辅助功能查找截点方式,一般配置为flagDefault默认方式。
android:accessibilityFeedbackType:操作相应按钮以后辅助功能给用户的反馈类型,包括声音,震动等。
android:notificationTimeout:相应时间设置
android:canRetrieveWindowContent:是否允许辅助功能获得窗口的节点信息,为了能正常实用辅助功能,请务必保持该项配置为true
配置完成后将demo跑进测试机器
进入 设置→辅助服务→无障碍
如果在“无障碍”下出现我们配置的服务,表示配置成功
实现微信语音电话状态监听
监听工作流程如下:
·捕获微信进入聊天状态
·启动语音录制服务
·捕获微信退出聊天状态
·退出语音录制服务
首先需要借助工具来分析微信的布局。通过捕获微信布局中的关键常量,来判断微信当前的状态。
布局分析工具
谷歌在AS 3.0之后,取消老旧的DDMS,为我们提供了界面更为友好的全新工具:LayoutInspector
打开方式:Tools → Lyaout Inspector 如图:
点击之后,系统弹出Choose Porcess提示我们选择需要监听的进程,这里我们选择微信,先不要点“OK”:
在微信上启动语音聊天,然后点击Choose Process的OK,选择VideoActivity,如图:
捕获到的微信聊天界面的布局如图所示:
通过分析左边的View Tree我们发现,微信中提示已接通、通话结束等状态的控件id为eg7.
这是一个很有用的发现,它的文本内容可以直接作为判断微信通话是否连接或者断开的依据。
ok 知道了微信语音聊天了开始和结束的状态判定依据,接下来就是用代码来实现监听的过程。
核心代码如下:
override fun onAccessibilityEvent(event: AccessibilityEvent){
//获取当前界面的布局信息
val noteInfo: AccessibilityNodeInfo = when {
rootInActiveWindow != null -> rootInActiveWindow
event.source != null -> event.source
else -> return
}
//获取id为eg7的控件 并根据内容执行相应的操作
val wechatToast = noteInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/eg7")
//如果id为eg7的控件存在
if (wechatToast.size>0){
//判断这个控件的内容
if (wechatToast[0].text.toString() == "已接通"){
//此时语音聊天已经接通
//TODO:do something you want
}else if (wechatToast[0].text.toString().contains("聊天结束")){
//此时语音聊天已经结束
//TODO:do something you want
}
}
}
至此,微信聊天状态已经捕获完成,TODO处可实现语音录制相关代码。
语音录制过程会在下一篇文章中详细介绍。