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

时间:2022-05-23 07:45:42

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

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

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

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

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

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

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

2.  广播

3.  AccessibilityService服务 

以下是对这三部分代码实现:

唤醒第三方应用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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先注册监听器

?
1
2
3
4
5
6
<!-- 注册监听手机状态 -->
 <receiver android:name=".PhoneReceiver">
  <intent-filter android:priority="1000" >
   <action android:name="android.intent.action.PHONE_STATE" />
  </intent-filter>
 </receiver>

相关权限

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

代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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服务实现:

相关权限及注册:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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配置文件:

?
1
2
3
4
5
6
7
8
9
10
<?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"
 />

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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。

项目源码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/fenzzz/article/details/50156697