accessibility是一个非常强大的功能,可以实现监听手机上的各种事件,比如窗口的变化,查找屏幕上当前显示的文字,以及模拟点击等功能,并且通过accessibility可以完成很多一般应用无法完成事件,比如发送物理或虚拟返回键的指令是通过如下代码实现的:
1 2 3 4 5 6 7 8 9 10 |
new Thread() { public void run() { try { Instrumentation inst = new Instrumentation(); inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); } catch (Exception e) { e.printStackTrace(); } } }.start(); |
该指令是需要在系统进程中运行的应用使用才会生效的命令,不过如果使用辅助功能就可以通过发送全局操作触发该操作.这样就可以完成在非root的手机上实现悬浮球的返回键的功能.
另外辅助功能还可以实现各种脚本和无root权限伪静默安装.绿色守护的非root模式和uc的静默安装还有那些抢红包的应用都是用该方式实现的,不过如果一些恶意应用拿到了辅助功能的权限是灾难性的.
辅助功能的开启关闭是在Android的系统设置中的辅助功能或者叫无障碍选项中.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * 当系统检测到一个匹配你辅助服务过滤器中设置参数的AccessibilityEvent时,调用该方法,运行时多次调用 * * @param event 在用户交互使用时系统返回的event事件 */ @Override public void onAccessibilityEvent(AccessibilityEvent event) /** * 当你的服务对系统事件的反馈被中断时,调用该方法。该方法同上面方法一样,也是被多次调用的 */ @Override public void onInterrupt() |
可以在res中创建xml文件夹,创建一个xml文件,文件名随意,在xml文件中对辅助服务进行配置,格式大致如下:
1 2 3 4 5 6 7 8 |
|
重写onServiceConnected() (当应用成功连接到你的辅助性服务时,系统调用该方法)在该方法中进行配置
1 2 3 4 5 6 7 |
AccessibilityServiceInfo 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()
的调用会失败。
1 2 3 4 5 6 7 8 |
|
name:对应的是自定义service的包名
label:对应了在系统辅助功能开关界面中,你的service的名字
description:则是点击对应的服务进入开关界面后,该服务的简介
permission:对应的权限(亦可在service中单独写出来)
1 |
|
intent-filter:指定了执行的组件为辅助功能类
如果是使用xml文件配置的辅助服务,还需要在service节点下添加meta-data节点,在resource指定自己编写的xml文件
1 2 3 |
|
可以使用 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
获取到节点之后即可使用节点进行点击等各种事件:
performAction(AccessibilityNodeInfo.ACTION_CLICK) //AccessibilityNodeInfo中有各种各样的事件以常量的形势声明. |
除了使用节点进行调用还可以调用全局事件:
返回键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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public boolean isAccessibilitySettingsOn(Context mContext) { int accessibilityEnabled = 0; final String service = getPackageName() + "/" + MyAccessibilityService.class.getCanonicalName(); //这里改成自己的class try { accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED); } catch (Settings.SettingNotFoundException ignored) { } TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':'); if (accessibilityEnabled == 1) { String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); if (settingValue != null) { mStringColonSplitter.setString(settingValue); while (mStringColonSplitter.hasNext()) { String accessibilityService = mStringColonSplitter.next(); if (accessibilityService.equalsIgnoreCase(service)) { return true; } } } } return false; } |
这个方法就是通过获取系统设置的存储辅助功能的数据库的内容提供者,然后查看已开的辅助功能的包名+类名
是否有自己,有就表示开了.(后面的用java程序悄悄打开对应的辅助功能其实就是去修改这个值)
打开系统设置辅助功能的界面:
1 2 |
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); startActivity(intent); |
在此之前先写一个简单的辅助功能应用:
编写一个类继承AccessibilityService,只在onStartCommand做一个返回键的全局操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class MyAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { } @Override public void onInterrupt() { } @Override 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 |
|
在清单文件进行注册
1 2 3 4 5 6 7 8 9 10 11 12 |
|
在Activity的xml文件中设置一个button,为了方便,声明onClick属性为onClick,在activity中编写onClick方法为如下内容
1 2 3 4 5 6 7 8 |
public 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,可以发现点击按钮后触发了返回键的功能.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class Temp { public static void main(String[] args) { System.out.println("running"); //这里是包名+辅助功能类名 String cmd1 = "settings put secure enabled_accessibility_services com.nesscurie.accessibility/com.nesscurie.accessibility.MyAccessibilityService"; String cmd2 = "settings put secure accessibility_enabled 1"; execShell(cmd1); execShell(cmd2); } //运行命令行的方法 private static void execShell(String cmd) { try { Process p = Runtime.getRuntime().exec(cmd); BufferedReader br = new BufferedReader(new InputStreamReader( p.getInputStream())); String readLine = br.readLine(); while (readLine != null) { System.out.println(readLine); readLine = br.readLine(); } if (br != null) { br.close(); } p.destroy(); p = null; } catch (IOException e) { e.printStackTrace(); } } } |
使用上一篇中的方法,将代码编译为.class后转为.dex,push到手机中,比如data/local/tmp目录:
adb shell cd data/local/tmp app_process -Djava.class.path=Temp.dex data/local/tmp Temp |
可以看到打印出runnig后,系统设置的该应用的辅助功能界面刷新为开,本来是需要系统权限(设置运行在系统进程中,具有系统权限)才能进行修改的选项,就这么悄悄的打开了.