2017.3.24日可用!以下是代码和说明
AndroidManifest.xml声明
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<service
android:name="com.zhonglou.showtencent.Server.RobServer"
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>
accessibility.xml
android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_description"
android:notificationTimeout="100"
android:packageNames="com.tencent.mm"
重要属性说明:
1.accessibilityEventTypes:表示该服务对界面中的哪些变化感兴趣,即哪些事件通知,比如窗口打开,滑动,焦点变化,长按等.具体的值可以在AccessibilityEvent类中查到,如typeAllMask表示接受所有的事件通知.
2.accessibilityFeedbackType:表示反馈方式,比如是语音播放,还是震动
3.canRetrieveWindowContent:表示该服务能否访问活动窗口中的内容.也就是如果你希望在服务中获取窗体内容的化,则需要设置其值为true.
4.notificationTimeout:接受事件的时间间隔,通常将其设置为100即可.
5.packageNames:表示对该服务是用来监听哪个包的产生的事件
RobServer.java
/** 该类提供的方法
* getRootInActiveWindow() 如果配置能够获取窗口内容,则会返回当前活动窗口的根结点
* getSystemService(String name)获取系统服务
* onAccessibilityEvent(AccessibilityEvent event) 有关AccessibilityEvent事件的回调函数.系统通过sendAccessibiliyEvent()不断的发送AccessibilityEvent到此处
* onGesture()
* onKeyEvent(KeyEvent event)如果允许服务监听按键操作,该方法是按键事件的回调,需要注意,这个过程发生了系统处理按键事件之前
* onServiceConnected()系统成功绑定该服务时被触发,通常我们可以在这里做一些初始化操作
*/
public class RobServer extends AccessibilityService {
private boolean newPacket = false;//有没新红包
private boolean enableKeyguard = true;//默认有屏幕锁
//锁屏、解锁相关
private KeyguardManager km;
private KeyguardManager.KeyguardLock kl;
//唤醒屏幕相关
private PowerManager pm;
private PowerManager.WakeLock wl = null;
//唤醒屏幕和解锁
private void wakeAndUnlock(boolean unLock)
{
if(unLock)
{
//若为黑屏状态则唤醒屏幕
if(!pm.isScreenOn()) {
//获取电源管理器对象,ACQUIRE_CAUSES_WAKEUP这个参数能从黑屏唤醒屏幕
wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "bright");
//点亮屏幕
wl.acquire();
}
//若在锁屏界面则解锁直接跳过锁屏
if(km.inKeyguardRestrictedInputMode()) {
//设置解锁标志,以判断抢完红包能否锁屏
enableKeyguard = false;
//解锁
kl.disableKeyguard();
}
}
else {
//如果之前解过锁则加锁以恢复原样
if(!enableKeyguard) {
//锁屏
kl.reenableKeyguard();
Log.i("demo", "加锁");
}
//若之前唤醒过屏幕则释放之使屏幕不保持常亮
if(wl != null) {
wl.release();
wl = null;
Log.i("demo", "关灯");
}
}
}
//初始化设置
@Override
protected void onServiceConnected() {
super.onServiceConnected();
//获取电源管理器对象
pm=(PowerManager)getSystemService(Context.POWER_SERVICE);
//得到键盘锁管理器对象
km= (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE);
//初始化一个键盘锁管理器对象
kl = km.newKeyguardLock("unLock");
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
/** AccessibilityEvent提供的方法
* getEventType() 获取事件类型
* getClassName() 获取事件源对应类的类型,比如点击事件是有某个Button产生的,那么此时获取的就是
* Button的完整类名
* getText() 获取事件源的文本信息,比如事件是有TextView发出的,此时获取的就是TextView的text属
* 性.如果该事件源是树结构,那么此时获取的是这个树上所有具有text属性的值的集合
* isEnabled() 事件源(对应的界面控件)是否处在可用状态
* getItemCount() 如果事件源是树结构,将返回该树根节点下子节点的数量
*/
int eventType = event.getEventType();
switch (eventType) {
case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:// 通知栏事件
/**抢红包步骤:检测通知栏事件是否有[微信红包],有则打开.
* 检测窗口改变事件,然后确定窗口是领红包界面,是则获取id/text
*/
List text = event.getText();
if (!text.isEmpty()) {
for (CharSequence text1 : text) {
String content = text1.toString();
if (content.contains("[微信红包]") || content.contains("[QQ红包]")) { //检测content中是否有该字符
wakeAndUnlock(true);
newPacket = true;
//监听到微信红包的notification,打开通知
if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
Notification notification = (Notification) event.getParcelableData();
PendingIntent pi = notification.contentIntent;
try {
pi.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}
}
}
}
break;
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: //窗口改变事件
String className = event.getClassName().toString();
if (className.equals("com.tencent.mm.ui.LauncherUI")) {
if (newPacket){
getPacket(); // 领取红包
}
} else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI")) {
openPacket();
wakeAndUnlock(false);
} else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI")) {
MoneyDetal(); //查看红包详情并退出
}
break;
}
}
/**
* 关闭红包详情界面,实现自动返回聊天窗口
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private void MoneyDetal() {
/**思路:微信中使用uiautomatorviewer查看布局,发现不同的手机相同的控件id是不一样的,比如我们需要查询获取红包
的数量时,需要先查找'元',然后获取其父控件,然后查找金额所在的位置,这个是不变的。 */
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
List list = new ArrayList<>();
List list1 = rootNode.findAccessibilityNodeInfosByText("元");
list.addAll(list1);
AccessibilityNodeInfo parent = list.get(0).getParent(); //群聊和私发不同,这是共同点
AccessibilityNodeInfo name = parent.getChild(0);
AccessibilityNodeInfo money = parent.getChild(2);
Log.d("classname", money.getText().toString());
Log.d("classname", name.getText().toString());
performBack(this);
}
/**
* 模拟点击,拆开红包
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void openPacket() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null) {
boolean isHave = false;
int count = nodeInfo.getChildCount();
for (int i = 0; i < count; i++) {
AccessibilityNodeInfo childNode = nodeInfo.getChild(i);
if ("android.widget.Button".equals(childNode.getClassName())) {
isHave = true;
childNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
if (!isHave) {
List list = nodeInfo.findAccessibilityNodeInfosByText("看看大家的手气");
if (list != null && list.size() > 0) {
performBack(this);
}
}
nodeInfo.recycle();
}
}
/**
* 模拟点击,打开抢红包界面
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void getPacket() {
AccessibilityNodeInfo rootinfo = getRootInActiveWindow();
List list = new ArrayList<>();
if (rootinfo != null &&rootinfo.getChildCount()>0){
List list1 = rootinfo.findAccessibilityNodeInfosByText("领取红包");
if (list1.size()>0){
list.addAll(list1);
}
for (int i = list.size() - 1; i >= 0; i--) {
AccessibilityNodeInfo parent = list.get(i).getParent();
if (parent != null) {
parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
rootinfo.recycle();
newPacket = false;
}
}
//模拟返回事件
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void performBack(AccessibilityService service) {
if(service == null) {
return;
}
service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
}
@Override
public void onInterrupt() {
Toast.makeText(this, "中断抢红包服务", Toast.LENGTH_SHORT)
.show();
}
@Override
public void onDestroy() {
super.onDestroy();
wakeAndUnlock(false);
}
}
注意事项,1.启动服务用intent启动,用的是startserver(intent)
别人图拿来用,Dump View Hierarchy for UI Automator可以获取你想要的class和布局方式,在DDMS那边。
扩展
2. APK自动安装
讲完了微信红包插件的实现原理,不难发现其本质是根据相关的界面状态,模拟后续的操作(比如点击等).
既然这样,那么我们完全可以利用该服务实现更多的功能,比如apk自动安装,传统的安装过程大概是如下流程:
点击apk文件,弹出安装信息界面,在该界面点击”下一步”,然后在点击”安装”,最后在安装完成界面点击”完成”.
不难发现,该流程完全可以通过模拟点击操作完成.现在我们简单的讲一下AccessibilityService在这方面的具体应用.我们知道系统的安装程序由PackageInstaller负责,其包名是com.android.packageinstaller,那么我们只需要监听该package下的安装信息界面和安装完成界面,并模拟点击”下一步”,”安装”,完成”“操作即可实现自动安装.
AccessibilityService配置如下:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"
android:description="@string/auto_service_des"
android:notificationTimeout="100"
android:packageNames="com.android.packageinstaller"/>
具体实现代码如下:
public class InstallService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d("InstallService", event.toString());
checkInstall(event);
}
private void checkInstall(AccessibilityEvent event) {
AccessibilityNodeInfo source = event.getSource();
if (source != null) {
boolean installPage = event.getPackageName().equals("com.android.packageinstaller");
if (installPage) {
installAPK(event);
}
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void installAPK(AccessibilityEvent event) {
AccessibilityNodeInfo source = getRootInActiveWindow();
List nextInfos = source.findAccessibilityNodeInfosByText("下一步");
nextClick(nextInfos);
List installInfos = source.findAccessibilityNodeInfosByText("安装");
nextClick(installInfos);
List openInfos = source.findAccessibilityNodeInfosByText("打开");
nextClick(openInfos);
runInBack(event);
}
private void runInBack(AccessibilityEvent event) {
event.getSource().performAction(AccessibilityService.GLOBAL_ACTION_BACK);
}
private void nextClick(List infos) {
if (infos != null)
for (AccessibilityNodeInfo info : infos) {
if (info.isEnabled() && info.isClickable())
info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private boolean checkTilte(AccessibilityNodeInfo source) {
List infos = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("@id/app_name");
for (AccessibilityNodeInfo nodeInfo : infos) {
if (nodeInfo.getClassName().equals("android.widget.TextView")) {
return true;
}
}
return false;
}
@Override
public void onInterrupt() {
}
@Override
protected void onServiceConnected() {
Log.d("InstallService", "auto install apk");
}
}
在很多情况下,我们需要检测自己的app是不是处在前台,借助该服务同样也能够完成该检测操作.下面,我们就演示一下如何实现:
AccessibilityService配置如下:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowStateChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"
android:description="@string/auto_detection"
android:notificationTimeout="100"/>
具体实现代码如下:
public class DetectionService extends AccessibilityService {
private static volatile String foregroundPackageName = "error";
/**
* 检测是否是前台服务
*
* @param packagenName
* @return
*/
public static boolean isForeground(String packagenName) {
return foregroundPackageName.equals(packagenName);
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
foregroundPackageName = event.getPackageName().toString();
}
}
@Override
public void onInterrupt() {
}
}
在使用时,需要引导用户开启该服务,之后通过调用DetectionService.isForeground()即可.
TYPE_VIEW_CLICKED
TYPE_VIEW_FOCUSED
TYPE_VIEW_LONG_CLICKED
TYPE_VIEW_SELECTED
TYPE_VIEW_TEXT_CHANGED
TYPE_WINDOW_STATE_CHANGED
TYPE_NOTIFICATION_STATE_CHANGED
TYPE_TOUCH_EXPLORATION_GESTURE_END
TYPE_ANNOUNCEMENT
TYPE_TOUCH_EXPLORATION_GESTURE_START
TYPE_VIEW_HOVER_ENTER
TYPE_VIEW_HOVER_EXIT
TYPE_VIEW_SCROLLED
TYPE_VIEW_TEXT_SELECTION_CHANGED
TYPE_WINDOW_CONTENT_CHANGED