Android自动化之AccessibilityService
demo示例说明
Manifest声明
AccessibilityService的XML配置文件
创建继承自AccessibilityService的服务类
MainActivity检测服务是否开启
UiAutomatorViewer
参考
简介
由于许多Android用户由于某些原因(视力,身体,年龄)要求他们以不同的方式与手机设备交互。
安卓提供了辅助功能特性和服务来帮助这些用户更容易的操作他们的设备,包括文字转语音(原生不支持中文,国内ROM可能会有,我的测试OPPO自带中文哟!),触觉反馈,手势操作,轨迹球和手柄操作。开发者可以利用这些服务使得程序更好用,也可以创建自己的辅助服务。
随着Android版本的不断升级,Android Accessibility功能也越来越强大,Android 4.0版本以前,系统辅助服务功能比较单一,仅仅能过单向获取窗口元素信息,比如获取输入框用户输入内容。到Android 4.1版本以后(现在主流厂商的版本都在KITKAT4.4),系统辅助服务增加了与窗口元素的双向交互,此时可以通过辅助功能服务操作窗口元素,比如点击按钮等。
由于系统辅助服务能够实时获取您当前操作应用的窗口元素信息,这有可能给你带来隐私信息的泄露风险,比如获取非密码输入框的输入内容等。同时通过辅助功能也可以模拟用户自动化点击应用内元素,也会带来一定的安全风险。
已经有其他同学使用AccessibilityService实现了抢红包~以及自动安装等,大家可以自行百度。
demo示例说明
接下来我们将通过AccessibilityService实现点击元素之后语言反馈所点击的内容。
测试辅助抓包应用为【微信】
下载地址
链接:http://pan.baidu.com/s/1pJVyo0z 密码:00i6
Manifest声明
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.leestar.example.accessibilityservice"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="21" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".AccessibilityServiceExample"
android:label="@string/accessibility_service_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_service_config" />
</service>
</application>
</manifest>
AccessibilityService的XML配置文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.leestar.example.accessibilityservice"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="21" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".AccessibilityServiceExample"
android:label="@string/accessibility_service_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_service_config" />
</service>
</application>
</manifest>
保存路径为<project_dir>/res/xml/accessibility_service_config.xml
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"
android:packageNames="com.tencent.mm"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackSpoken"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"
/>
其中
android:packageNames为需要捕获的包名
android:accessibilityEventTypes为需要回调的事件
android:notificationTimeout为事件回调的延迟事件
其他具体含义请查询官方文档~
创建继承自AccessibilityService的服务类
具体见代码以及注释,里面也实现了语言反馈功能。
package com.leestar.example.accessibilityservice;
import android.accessibilityservice.AccessibilityService;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
public class AccessibilityServiceExample extends AccessibilityService {
private AccessibilityNodeInfo rootNodeInfo;
private TextToSpeech tts = null;
private boolean ttsIsInit = false;
/**
* 可选。系统会在成功连接上你的服务的时候调用这个方法,在这个方法里你可以做一下初始化工作,例如设备的声音震动管理,
* 也可以调用setServiceInfo()进行配置工作。
*/
@Override
protected void onServiceConnected() {
// TODO Auto-generated method stub
super.onServiceConnected();
tts = new TextToSpeech(getApplicationContext(), new android.speech.tts.TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
ttsIsInit = true;
}
}
});
}
/**
* 可选。在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。
*/
@Override
public boolean onUnbind(Intent intent) {
// TODO Auto-generated method stub
return super.onUnbind(intent);
}
/**
* 根据XML的android:accessibilityEventTypes来进行事件回调,所有的业务逻辑都要在回调中完成
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
rootNodeInfo = event.getSource();
if (rootNodeInfo == null)
return;
switch (event.getEventType()) {
case AccessibilityEvent.TYPE_VIEW_CLICKED:
// 语音反馈
if (ttsIsInit) {
// event.getContentDescription根据触发事件节点来自动查找子树的ContentDescription
//所以你会发现UIAutomatorViewer上的相关节点信息为空,但是event里赋值了。
// event.getText同上
if (event.getContentDescription() != null) {
tts.speak(event.getContentDescription().toString(), TextToSpeech.QUEUE_ADD, null);
} else if (event.getText() != null) {
tts.speak(event.getText().toString(), TextToSpeech.QUEUE_ADD, null);
}
}
// 获取精确节点信息,可以通过UIAutomatorViewer检索,实现相关模拟操作
//AccessibilityNodeInfo提供了2种自动查找字节点的函数
//rootNodeInfo.findAccessibilityNodeInfosByText
//rootNodeInfo.findAccessibilityNodeInfosByViewId(viewId)
if (rootNodeInfo.getChild(0) != null
&& rootNodeInfo.getChild(0).getClassName().equals("android.widget.TextView")
&& rootNodeInfo.getChild(0).getText() != null
&& rootNodeInfo.getChild(0).getText().toString().equals("我")) {
// 如果发现按的是【我】,模拟全局返回按钮,即退出微信
performGlobalAction(GLOBAL_ACTION_BACK);
// 模拟点击元素
// if (rootNodeInfo.isClickable()) {
// rootNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
// }
}
break;
default:
break;
}
Log.i("leestar", AccessibilityEvent.eventTypeToString(event.getEventType()));
Log.i("leestar", rootNodeInfo.getClassName().toString());
}
/**
* 这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。
*/
@Override
public void onInterrupt() {
Log.i("leestar", "onInterrupt");
}
}
MainActivity检测服务是否开启
package com.leestar.example.accessibilityservice;
import java.util.List;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
public class MainActivity extends Activity {
private Button button_switch;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button_switch = (Button) findViewById(R.id.button_switch);
}
@Override
protected void onResume() {
super.onResume();
updateServiceStatus();
}
@Override
protected void onDestroy() {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
super.onDestroy();
}
public void onButtonClick(View view) {
startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
}
private void updateServiceStatus() {
boolean ServiceEnabled = false;
// 循环遍历所有服务,查看是否开启
AccessibilityManager accessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
for (AccessibilityServiceInfo info : accessibilityServices) {
if (info.getId().equals(getPackageName() + "/.AccessibilityServiceExample")) {
ServiceEnabled = true;
break;
}
}
if (ServiceEnabled) {
button_switch.setText("关闭测试服务");
// Prevent screen from dimming
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
button_switch.setText("开启测试服务");
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
}
UiAutomatorViewer
package com.leestar.example.accessibilityservice;
import java.util.List;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
public class MainActivity extends Activity {
private Button button_switch;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button_switch = (Button) findViewById(R.id.button_switch);
}
@Override
protected void onResume() {
super.onResume();
updateServiceStatus();
}
@Override
protected void onDestroy() {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
super.onDestroy();
}
public void onButtonClick(View view) {
startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
}
private void updateServiceStatus() {
boolean ServiceEnabled = false;
// 循环遍历所有服务,查看是否开启
AccessibilityManager accessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
for (AccessibilityServiceInfo info : accessibilityServices) {
if (info.getId().equals(getPackageName() + "/.AccessibilityServiceExample")) {
ServiceEnabled = true;
break;
}
}
if (ServiceEnabled) {
button_switch.setText("关闭测试服务");
// Prevent screen from dimming
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
button_switch.setText("开启测试服务");
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
}
在需要精确元素节点操作检索的时候,就需要用到UiAutomatorViewer工具,一般情况下,Android SDK下会自带这个工具
找到uiautomatorviewer.bat即可,打开之后,点击看起来是这个样子的
我想你应该知道怎么做了,树形结构UI,AccessibilityNodeInfo通过getChild、getParent尽情的识别吧~!
参考
http://www.android-doc.com/guide/topics/ui/accessibility/services.html
Android自动化之AccessibilityService的更多相关文章
- Android自动化框架介绍
随着Android应用得越来越广,越来越多的公司推出了自己移动应用测试平台.例如,百度的MTC.东软易测云.Testin云测试平台…….由于自己所在项目组就是做终端测试工具的,故抽空了解了下几种常见的 ...
- Android自动化测试中AccessibilityService获取控件信息(1)
Android自动化测试中AccessibilityService获取控件信息(1) 分类: android自动化测试2014-03-24 15:31 3455人阅读 评论(16) 收藏 举报 and ...
- Android自动化框架 模拟操作 模拟测试
转自:http://bbs2.c114.net/home.php?mod=space&uid=1025779&do=blog&id=5322 几种常见的Android自动化测试 ...
- Android自动化框架
Android自动化框架 已有 2085 次阅读2014-8-26 12:19 | Android 几种常见的Android自动化测试框架及其应用 随着Android应用得越来越广,越来越多的公司推出 ...
- Android自动化学习笔记:编写MonkeyRunner脚本的几种方式
---------------------------------------------------------------------------------------------------- ...
- Android自动化学习笔记之MonkeyRunner:官方介绍和简单实例
---------------------------------------------------------------------------------------------------- ...
- Android自动化测试之Monkeyrunner学习笔记(一)
Android自动化测试之Monkeyrunner学习笔记(一) 因项目需要,开始研究Android自动化测试方法,对其中的一些工具.方法和框架做了一些简单的整理,其中包括Monkey.Monkeyr ...
- Android自动化压力测试图解教程——Monkey工具
[置顶] Android自动化压力测试图解教程--Monkey工具 标签: 测试androidprofiling工具测试工具文档 2012-04-01 10:16 38185人阅读 评论(10) 收藏 ...
- android自动化环境搭建
android自动化环境安装指南 1.appium相关安装(eclipse下)见http://www.cnblogs.com/wangcp-2014/p/5717589.html参考selenium的 ...
随机推荐
- Awk使用方法简介
==================AWK=================== AWK简介:awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得 ...
- linux和windows下的命令
1.rz命令,可以传输文件 2.kill -9 杀死进程 3.windows命令: netstat -ano | findstr "3031" taskkill /f /t /i ...
- Oracle数据表转换为Shapefile(二)
在上一篇博文<Oracle数据表转换为Shapefile(一)>中详细描述了一种基于Oracle数据表生产Shapefile的技术方法,本文同样以详细图解的方式描述一种更便捷的方法来完成同 ...
- 数据库架构设计的三种模式:share nothing , share everythong , share disk
数据库构架设计中主要有Shared Everthting.Shared Nothing.和Shared Disk: Shared Everthting:一般是针对单个主机,完全透明共享CPU/MEMO ...
- (原创)Log4Net 在多层项目中的使用小记
这几天刚好在调整一个项目,把一些自己不是很清楚的东西先试验一下,这篇文章主要是对我在项目中需要使用Log4Net的一些记录.网上有很多相关的教程,但是各有各的说法,我结合我自己这个项目的需要,首先,项 ...
- Nginx源码编译
1. 概述 有时由于添加了自己编写的c++模块,或者改了源码的一些名称.配置什么的,需要自行编译nginx. 可以先下下来源码,然后需要的话就自己改下源码或增加模块,最后再编译成linux或者wind ...
- C++视频教程学习笔记
1. 命名空间 用于解决命名冲突的问题 里面可以放函数.变量.结构体.类 可以嵌套 必须定义在全局作用域下 是开放的,可以随时往原先的命名空间中追加内容,而不是覆盖 实现命名空间下的函数和调用时,需要 ...
- on where having总结
1. ON 和WHERE 所有的查询都回产生一个中间临时报表,查询结果就是从返回临时报表中得到.ON和WHERE后面所跟限制条件的区别,主要与限制条件起作用的时机有关, ON根据限制条件对数据库记录进 ...
- 如何防止Unity3D代码被反编译?
欢迎访问网易云社区,了解更多网易技术产品运营经验. 网易云易盾移动游戏安全技术专家陈士留在2018年Unity技术路演演讲内容中对这个问题有过比较详细的介绍,摘录如下: 防止Unity3D代码被反编译 ...
- 苹果软件App上架问题
0.官方网站 开发者中心 itunes connect 优酷 哔哩哔哩 腾讯视频 1.上架流程 1.1 开发者账号申请 2017年苹果企业开发者账请完号申整指南 iOS开发之苹果开发者账号注册申请流程 ...