zoukankan      html  css  js  c++  java
  • 基于AccessibilityService制作的钉钉自动签到程序

    基于AccessibilityService制作的钉钉自动签到程序
    标签: 移动开发安卓自动化操作
    2015-12-03 09:56 1736人阅读 评论(10) 收藏 举报
    分类:
    Android(3) 
    版权声明:本文为博主原创文章,未经博主允许不得转载。
    前两天公司开始宣布要使用阿里钉钉来签到啦!!!~~这就意味着,我必须老老实实每天按时签到上班下班了,这真是一个悲伤的消息,可是!!!!那么机智(lan)的我,怎么可能就这么屈服!!!阿里钉钉签到,说到底不就是手机软件签到吗?我就是干移动开发的,做一个小应用每天自动签到不就行了:)
    说干就干,首先分析一下,阿里钉钉的签到流程:
    打开阿里钉钉->广告页停留2S左右->进入主页->点击“工作”tab->点击“签到”模块->进入签到页面(可能会再次出现广告和对话框)->点击签到
    我们操作手机的过程就是这样,要实现这些点击,很自然想起了前段时间做的微信抢红包小应用,利用AccessibilityService服务帮助我们实现这些自动化操作。
    以上是分析过程,接下来是我对这个小功能实现的具体方案思路:
    将测试手机放公司并且安装这个应用,通过我远程的电话拨打或者短信发送到测试手机(只要能产生广播或者信息的就行),测试手机接受到广播信息,唤醒钉钉,进入钉钉页面,AccessibilityService开始工作,进行一系列点击签到操作,结束操作后退出钉钉,签到完成。
    通过以上过程的分析我们大概要用到的知识有以下几块:
    唤醒非自己的其他第三方应用
    广播
    AccessibilityService服务
     
    以下是对这三部分代码实现:
    唤醒第三方应用
    package net.fenzz.dingplug;
     
    import java.util.List;
     
    import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.pm.PackageInfo;
    import android.content.pm.PackageManager;
    import android.content.pm.PackageManager.NameNotFoundException;
    import android.content.pm.ResolveInfo;
     
    public class Utils {
     
        public static void openCLD(String packageName,Context context) {
            PackageManager packageManager = context.getPackageManager();
            PackageInfo pi = null;  
     
                try {
     
                    pi = packageManager.getPackageInfo("com.alibaba.android.rimet", 0);
                } catch (NameNotFoundException e) {
     
                }
                Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null);
                resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER);
                resolveIntent.setPackage(pi.packageName);
     
                List<ResolveInfo> apps = packageManager.queryIntentActivities(resolveIntent, 0);
     
                ResolveInfo ri = apps.iterator().next();
                if (ri != null ) {
                    String className = ri.activityInfo.name;
     
                    Intent intent = new Intent(Intent.ACTION_MAIN);
                    intent.addCategory(Intent.CATEGORY_LAUNCHER);
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    ComponentName cn = new ComponentName(packageName, className);
     
                    intent.setComponent(cn);
                    context.startActivity(intent);
                }
        }
     
    }

    接受电话广播并且唤醒钉钉:
    mainifest先注册监听器
      <!-- 注册监听手机状态 --> 
            <receiver android:name=".PhoneReceiver"> 
                <intent-filter android:priority="1000" > 
                    <action android:name="android.intent.action.PHONE_STATE" /> 
                </intent-filter> 
            </receiver> 

    相关权限
     <!-- 读取手机状态的权限 --> 
        <uses-permission android:name="android.permission.READ_PHONE_STATE" /> 
         <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>

    代码
    package net.fenzz.dingplug;
     
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.telephony.TelephonyManager;
    import android.app.Service;
    import android.util.Log;
     
    public class PhoneReceiver extends BroadcastReceiver {
     
        private static final String TAG = "message";
        private static boolean mIncomingFlag = false;
        private static String mIncomingNumber = null;
     
        @Override
        public void onReceive(Context context, Intent intent) {
            // 如果是拨打电话
            if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
                mIncomingFlag = false;
                String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
                Log.i(TAG, "call OUT:" + phoneNumber);
     
            } else {
                // 如果是来电
                TelephonyManager tManager = (TelephonyManager) context
                        .getSystemService(Service.TELEPHONY_SERVICE);
                switch (tManager.getCallState()) {
     
                case TelephonyManager.CALL_STATE_RINGING:
                    mIncomingNumber = intent.getStringExtra("incoming_number");
                    Log.i(TAG, "RINGING :" + mIncomingNumber);
                    if(mIncomingNumber!=null&&mIncomingNumber.equals(你的手机号)){
                        Utils.openCLD("com.alibaba.android.rimet", context);
                        DingService.instance.setServiceEnable();
                    }
                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK:
                    if (mIncomingFlag) {
                        Log.i(TAG, "incoming ACCEPT :" + mIncomingNumber);
                    }
                    break;
                case TelephonyManager.CALL_STATE_IDLE:
                    if (mIncomingFlag) {
                        Log.i(TAG, "incoming IDLE");
                    }
                    break;
                }
            }
    }
    AccessibilityService服务实现:
    相关权限及注册:
    <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
     
    <service
                android:name=".DingService"
                android:enabled="true"
                android:exported="true"
                android:label="@string/app_name"
                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/red_service_config" />
    </service>

    需要在res文件夹下新建一个xml文件夹里面放入一个这样的xml配置文件:
    <?xml version="1.0" encoding="utf-8"?>
    <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/accessibility_description"
        android:notificationTimeout="100"
        android:packageNames="com.alibaba.android.rimet"
     
        />

    代码:
    package net.fenzz.dingplug;
     
    import java.util.ArrayList;
    import java.util.List;
     
    import android.accessibilityservice.AccessibilityService;
    import android.util.Log;
    import android.view.accessibility.AccessibilityEvent;
    import android.view.accessibility.AccessibilityNodeInfo;
    import android.widget.Toast;
     
    public class DingService extends AccessibilityService {
     
        private String TAG = getClass().getSimpleName();
     
        private  boolean  isFinish = false;
     
        public static DingService instance;
        private int index = 1;
     
        /**
         * 获取到短信通知
         *  0.唤醒屏幕
         *  1.打开钉钉
         *  2.确保当前页是主页界面
         *  3.找到“工作”tab并且点击
         *  4.确保到达签到页面
         *  5.找到签到按钮,并且点击
         *  6.判断签到是否成功
         *      1.成功,退出程序
         *      2.失败,返回到主页,重新从1开始签到
         */
     
     
        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
            // TODO Auto-generated method stub
    //       final int eventType = event.getEventType();
             ArrayList<String> texts = new ArrayList<String>();
                Log.i(TAG, "事件---->" + event.getEventType());
     
     
             if(isFinish){
                return;
             }
     
             AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
             if(nodeInfo == null) {
                    Log.w(TAG, "rootWindow为空");
                    return ;
              }
    //       nodeInfo.
     
    //       System.out.println("nodeInfo"+nodeInfo);
     
     
     
             System.out.println("index:"+index);
             switch (index) {
     
            case 1: //进入主页
                 OpenHome(event.getEventType(),nodeInfo);
                break;
            case 2: //进入签到页
                OpenQianDao(event.getEventType(),nodeInfo);
                break;
            case 3:
                doQianDao(event.getEventType(),nodeInfo);
                break;
     
            default:
                break;
            }
     
        }
     
     
        private ArrayList<String> getTextList(AccessibilityNodeInfo node,ArrayList<String> textList){
            if(node == null) {
                Log.w(TAG, "rootWindow为空");
                return null;
            }
            if(textList==null){
                textList = new ArrayList<String>();
            }
            String text = node.getText().toString();
              if(text!=null&&text.equals("")){
                  textList.add(text);
              }
    //        node.get
            return null;
     
        }
     
     
        private void OpenHome(int type,AccessibilityNodeInfo nodeInfo) {
            if(type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){
                //判断当前是否是钉钉主页
                List<AccessibilityNodeInfo> homeList = nodeInfo.findAccessibilityNodeInfosByText("工作");
                if(!homeList.isEmpty()){
                    //点击
                     boolean isHome = click( "工作");
                     System.out.println("---->"+isHome);
                    index = 2;
                    System.out.println("点击进入主页签到");
                }
            }
     
        }
     
        private void OpenQianDao(int type,AccessibilityNodeInfo nodeInfo) {
            if(type == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED){
                //判断当前是否是主页的签到页
                List<AccessibilityNodeInfo> qianList = nodeInfo.findAccessibilityNodeInfosByText("工作");
                if(!qianList.isEmpty()){
                     boolean ret = click( "签到");
                     index = 3;
                     System.out.println("点击进入签到页面详情");
                }
     
    //           index = ret?3:1;  
            }
     
        }
     
     
        private void doQianDao(int type,AccessibilityNodeInfo nodeInfo) {
            if(type == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED){
                //判断当前页是否是签到页
                List<AccessibilityNodeInfo> case1 = nodeInfo.findAccessibilityNodeInfosByText("开启我的签到之旅");
                if(!case1.isEmpty()){
                    click("开启我的签到之旅");
                    System.out.println("点击签到之旅");
                }
     
                List<AccessibilityNodeInfo> case2 = nodeInfo.findAccessibilityNodeInfosByText("我知道了");
                if(!case2.isEmpty()){
                    click("我知道了");
                    System.out.println("点击我知道对话框");
                }
                List<AccessibilityNodeInfo> case3 = nodeInfo.findAccessibilityNodeInfosByText("签到");
                if(!case3.isEmpty()){
                    Toast.makeText(getApplicationContext(), "发现目标啦!!~~", 1).show();
                    System.out.println("发现目标啦!");
                    click("签到");
                    isFinish = true;
                }
            }
     
    //      if(type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){
    //          List<AccessibilityNodeInfo> case3 = nodeInfo.findAccessibilityNodeInfosByText("签到");
    //          if(!case3.isEmpty()){
    //              Toast.makeText(getApplicationContext(), "发现目标啦!!~~", 1).show();
    //          }
    //      }
     
        }
     
     
        //通过文字点击
        private boolean click(String viewText){
             AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
            if(nodeInfo == null) {
                    Log.w(TAG, "点击失败,rootWindow为空");
                    return false;
            }
            List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(viewText);
            if(list.isEmpty()){
                //没有该文字的控件
                 Log.w(TAG, "点击失败,"+viewText+"控件列表为空");
                 return false;
            }else{
                //有该控件
                //找到可点击的父控件
                AccessibilityNodeInfo view = list.get(0);
                return onclick(view);  //遍历点击
            }
     
        }
     
        private boolean onclick(AccessibilityNodeInfo view){
            if(view.isClickable()){
                view.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                 Log.w(TAG, "点击成功");
                 return true;
            }else{
     
                AccessibilityNodeInfo parent = view.getParent();
                if(parent==null){
                    return false;
                }
                onclick(parent);
            }
            return false;
        }
     
        //点击返回按钮事件
        private void back(){
             performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
        }
     
        @Override
        public void onInterrupt() {
            // TODO Auto-generated method stub
     
        }
     
        @Override
        protected void onServiceConnected() {
            // TODO Auto-generated method stub
            super.onServiceConnected();
            Log.i(TAG, "service connected!");
            Toast.makeText(getApplicationContext(), "连接成功!", 1).show();
            instance = this;
        }
     
        public void setServiceEnable(){
            isFinish = false;
            Toast.makeText(getApplicationContext(), "服务可用开启!", 1).show();
            index = 1;
        }
     
    }

    以上基本是所有代码,这个小程序中可以不用Activity组件,也可以加一个小的Activity,用来作为系统的总开关,当然也可以自动检测时间,来判断是否开启服务,这样就不用Activity了,在这个小例子中,我使用了一个小activity,就放了一个button。
    项目源码,我稍后上传!




  • 相关阅读:
    Java 的this和super关键字
    Java关于访问控制权限
    Java 封装 继承 多态
    Java的集成开发工具
    如何查询小程序中的代码量
    小程序中使用echarts及使用的坑
    微信小程序-新的页面授权机制
    前端处理几十万条数据不卡顿(window.requestAnimationFrame)
    Vs code中Eslint 与 Prettier格式化冲突
    从原型与原型链的角度看es6 class
  • 原文地址:https://www.cnblogs.com/Jxiaobai/p/6617665.html
Copyright © 2011-2022 走看看