AccessibilityService类使用和抢红包功能的实现

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");
    }
}
  1. 检测前台服务:

在很多情况下,我们需要检测自己的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

你可能感兴趣的:(最新组合应用,android)