Android Accessibility大致解析,通过adb运行纯java代码打开应用的辅助功能
CommentAndroid的辅助功能accessibility的具体文档可以查看:google accessibility说明文档
accessibility是一个非常强大的功能,可以实现监听手机上的各种事件,比如窗口的变化,查找屏幕上当前显示的文字,以及模拟点击等功能,并且通过accessibility可以完成很多一般应用无法完成事件,比如发送物理或虚拟返回键的指令是通过如下代码实现的:
1 | new Thread() { |
该指令是需要在系统进程中运行的应用使用才会生效的命令,不过如果使用辅助功能就可以通过发送全局操作触发该操作.这样就可以完成在非root的手机上实现悬浮球的返回键的功能.
另外辅助功能还可以实现各种脚本和无root权限伪静默安装.绿色守护的非root模式和uc的静默安装还有那些抢红包的应用都是用该方式实现的,不过如果一些恶意应用拿到了辅助功能的权限是灾难性的.
辅助功能的开启关闭是在Android的系统设置中的辅助功能或者叫无障碍选项中.
如何编写一个辅助功能服务
1.创建一个class继承于AccessibilityService,会强制重写2个方法
1 | /** |
2.接下来有2种选择
可以在res中创建xml文件夹,创建一个xml文件,文件名随意,在xml文件中对辅助服务进行配置,格式大致如下:
1
2
3
4
5
6
7
8<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeNotificationStateChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagReportViewIds"
android:description="@string/app_name"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
android:packageNames="com.nesscurie.accessibility" />重写onServiceConnected() (当应用成功连接到你的辅助性服务时,系统调用该方法)在该方法中进行配置
1
2
3
4
5
6
7AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
serviceInfo.eventTypes = ...;
serviceInfo.feedbackType = ...;
serviceInfo.notificationTimeout = ...;
serviceInfo.packageNames = new String[]{...};
serviceInfo.flags = ...;
setServiceInfo(serviceInfo);
各属性解释:
accessibilityEventTypes / eventTypes
事件类型typeAllMask / AccessibilityEvent.TYPES_ALL_MASK
全局事件响应
typeViewClicked / AccessibilityEvent.TYPE_VIEW_CLICKED
点击事件accessibilityFeedbackType / feedbackType
反馈方式feedbackGeneric / AccessibilityServiceInfo.FEEDBACK_GENERIC
通用的反馈
feedbackAudible / AccessibilityServiceInfo.FEEDBACK_AUDIBLE
声音反馈
feedbackSpoken / AccessibilityServiceInfo.FEEDBACK_SPOKEN
语音反馈notificationTimeout / notificationTimeout
响应毫秒值packageNames
监听的应用的包名,可指定多个包名,xml文件中使用,隔开accessibilityFlags / flags
用于之后node.getViewIdResourceName()
的权限description
辅助功能的描述canRetrieveWindowContent
从一个AccessibilityEvent中调查完全视图层级的能力隐式地暴露私有用户信息给你的辅助服务,必须通过配置XML文件请求这个级别的访问权,不在你的服务配置xml文件中包含这个设置,那么对getSource()
的调用会失败。
3.在manifest中注册辅助功能的服务
1 | <service |
name:对应的是自定义service的包名
label:对应了在系统辅助功能开关界面中,你的service的名字
description:则是点击对应的服务进入开关界面后,该服务的简介
permission:对应的权限(亦可在service中单独写出来)
1 | <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"/> |
intent-filter:指定了执行的组件为辅助功能类
如果是使用xml文件配置的辅助服务,还需要在service节点下添加meta-data节点,在resource指定自己编写的xml文件
1 | <meta-data |
4.接下来就可以在onAccessibilityEvent(AccessibilityEvent event)处理各种事件:
可以使用 event.getEventType()来获取各种事件消息:
基本窗口view的变化都可以使用这个type来监听:
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED
打开popupwindow,菜单,对话框时候会触发:
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
更加精确的代表了基于当前event.source中的子view的内容变化:
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
窗口的变化:
AccessibilityEvent.TYPE_WINDOWS_CHANGED
当前event的节点信息.有两种方式获取:
使用event获取:
AccessibilityNodeInfo nodeInfo = event.getSource();
在accessibility中直接获取:
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
使用完要记得调用recycle();进行释放以免内存泄漏
nodeInfo.recycle();
接下来就可以使用下面的方法来获取对应的所有节点的集合
findAccessibilityNodeInfosByViewId(String str)
findAccessibilityNodeInfosByText(String str)
获取id的方法:
使用DDMS的hierarchy View来查找对应的viewId,但是有很多手机是没有办法获取,只会提示:Unable to get view server version from device XXXXX
在这个时候,在AccessibiltiyService的配置中添加的flag。flagReportViewIds就可以派上用场了
在窗口改变时,获取并遍历所有的node,即打印出node对应的文字和id
传入方法id的格式为: 应用的包名 + “:id/“ + 获取到的id
获取到节点之后即可使用节点进行点击等各种事件:
1 | performAction(AccessibilityNodeInfo.ACTION_CLICK) |
除了使用节点进行调用还可以调用全局事件:
返回键performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
HOME键performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
最近打开应用列表performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS);
打开通知栏performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
锁屏performGlobalAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG);
设置performGlobalAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
判断对应的辅助功能有没有打开的方法:
1 | public boolean isAccessibilitySettingsOn(Context mContext) { |
这个方法就是通过获取系统设置的存储辅助功能的数据库的内容提供者,然后查看已开的辅助功能的包名+类名
是否有自己,有就表示开了.(后面的用java程序悄悄打开对应的辅助功能其实就是去修改这个值)
打开系统设置辅助功能的界面:
1 | Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); |
辅助功能的介绍就是这些,下面开始使用adb运行java程序悄悄打开辅助功能:系统设置的各个选项通过数据库形式进行保存,通过user为shell的高权限将系统设置存储的配置进行修改.
在此之前先写一个简单的辅助功能应用:
编写一个类继承AccessibilityService,只在onStartCommand做一个返回键的全局操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class MyAccessibilityService extends AccessibilityService {
public void onAccessibilityEvent(AccessibilityEvent event) {
}
public void onInterrupt() {
}
public int onStartCommand(Intent intent, int flags, int startId) {
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
return super.onStartCommand(intent, flags, startId);
}
}xml配置最普通的内容
1
2
3
4
5
6
7
8<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeNotificationStateChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:description="@string/app_name"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
android:packageNames="com.nesscurie.accessibility" />在清单文件进行注册
1
2
3
4
5
6
7
8
9
10
11
12<service
android:name=".MyAccessibilityService"
android:label="我的辅助功能"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_config" />
</service>在Activity的xml文件中设置一个button,为了方便,声明onClick属性为onClick,在activity中编写onClick方法为如下内容
1
2
3
4
5
6
7
8public void onClick(View view) {
if (!isAccessibilitySettingsOn(this)) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
return;
}
startService(new Intent(this, MyAccessibilityService.class));
}
部署到设备上,打开应用点击按钮可以发现打开了系统设置的辅助功能的界面,开启对应的辅助功能,回到app,可以发现点击按钮后触发了返回键的功能.
关闭该应用辅助功能,接下来就是编写能修改系统设置的纯java代码:使用adb的settings命令将设置的数据库中对应字段改变
1 | public class Temp { |
使用上一篇中的方法,将代码编译为.class后转为.dex,push到手机中,比如data/local/tmp目录:
1 | adb shell |
可以看到打印出runnig后,系统设置的该应用的辅助功能界面刷新为开,本来是需要系统权限(设置运行在系统进程中,具有系统权限)才能进行修改的选项,就这么悄悄的打开了.
辅助功能是一个非常强大的功能,不仅能为不少不方便的人群提供使用手机的方式,还可以带来不少特殊的体验,不过国内大部分app都没有针对少数不方便的人群进行辅助功能的开发,并且很多时候辅助功能被用在了不正确的途径,不禁是一件令人深思的事情.