Android基于AccessibilityService制作的钉钉自动签到程序代码

作者:袖梨 2022-06-25

前两天公司开始宣布要使用阿里钉钉来签到啦!!!~~这就意味着,我必须老老实实每天按时签到上班下班了,这真是一个悲伤的消息,可是!!!!那么机智(lan)的我,怎么可能就这么屈服!!!阿里钉钉签到,说到底不就是手机软件签到吗?我就是干移动开发的,做一个小应用每天自动签到不就行了:)

说干就干,首先分析一下,阿里钉钉的签到流程:
打开阿里钉钉->广告页停留2S左右->进入主页->点击“工作”tab->点击“签到”模块->进入签到页面(可能会再次出现广告和对话框)->点击签到

我们操作手机的过程就是这样,要实现这些点击,很自然想起了前段时间做的微信抢红包小应用,利用AccessibilityService服务帮助我们实现这些自动化操作。

以上是分析过程,接下来是我对这个小功能实现的具体方案思路:

将测试手机放公司并且安装这个应用,通过我远程的电话拨打或者短信发送到测试手机(只要能产生广播或者信息的就行),测试手机接受到广播信息,唤醒钉钉,进入钉钉页面,AccessibilityService开始工作,进行一系列点击签到操作,结束操作后退出钉钉,签到完成。

通过以上过程的分析我们大概要用到的知识有以下几块:

1.  唤醒非自己的其他第三方应用

2.  广播

3.  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 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先注册监听器

  
   
    
     
    
   

相关权限

 
  
  

代码

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服务实现:

相关权限及注册: 



   
    
   
   
 

需要在res文件夹下新建一个xml文件夹里面放入一个这样的xml配置文件:




代码:

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 texts = new ArrayList();
   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 getTextList(AccessibilityNodeInfo node,ArrayList textList){
  if(node == null) {
   Log.w(TAG, "rootWindow为空");
   return null;
  }
  if(textList==null){
   textList = new ArrayList();
  }
  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 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 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 case1 = nodeInfo.findAccessibilityNodeInfosByText("开启我的签到之旅");
   if(!case1.isEmpty()){
    click("开启我的签到之旅");
    System.out.println("点击签到之旅");
   }
   List case2 = nodeInfo.findAccessibilityNodeInfosByText("我知道了");
   if(!case2.isEmpty()){
    click("我知道了");
    System.out.println("点击我知道对话框");
   }
   List 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 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 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。

相关文章

精彩推荐