基于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。
项目源码,我稍后上传!




posted @ 2017-03-25 17:05  Mr.xiaobai丶  阅读(550)  评论(0编辑  收藏  举报