├── README.md
└── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── com
│ └── ileja
│ └── autoreply
│ └── ApplicationTest.java
├── main
├── AndroidManifest.xml
├── java
│ └── com
│ │ └── ileja
│ │ └── autoreply
│ │ ├── AutoReplyService.java
│ │ └── MainActivity.java
└── res
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ ├── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── auto_reply_service_config.xml
└── test
└── java
└── com
└── ileja
└── autoreply
└── ExampleUnitTest.java
/README.md:
--------------------------------------------------------------------------------
1 | # WcAutoReply
2 |
3 | Android 微信自动回复功能,使用的知识为 AccessibilityService,未来有时间会重新研究下各种 rom 下的适配,以及优化下代码 :)
4 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 | defaultConfig {
7 | applicationId "com.ileja.autoreply"
8 | minSdkVersion 19
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | productFlavors {
20 | }
21 | }
22 |
23 | dependencies {
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | testCompile 'junit:junit:4.12'
26 | compile 'com.android.support:appcompat-v7:23.4.0'
27 | compile 'com.jakewharton:butterknife:7.0.1'
28 | compile 'com.jakewharton.timber:timber:2.4.2'
29 | }
30 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/chentao/Documents/android/develop/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ileja/autoreply/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.ileja.autoreply;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ileja/autoreply/AutoReplyService.java:
--------------------------------------------------------------------------------
1 | package com.ileja.autoreply;
2 |
3 | import android.accessibilityservice.AccessibilityService;
4 | import android.annotation.SuppressLint;
5 | import android.app.ActivityManager;
6 | import android.app.KeyguardManager;
7 | import android.app.Notification;
8 | import android.app.PendingIntent;
9 | import android.content.ClipData;
10 | import android.content.ClipboardManager;
11 | import android.content.ComponentName;
12 | import android.content.Context;
13 | import android.content.Intent;
14 | import android.os.Bundle;
15 | import android.os.Handler;
16 | import android.os.PowerManager;
17 | import android.text.TextUtils;
18 | import android.view.KeyEvent;
19 | import android.view.accessibility.AccessibilityEvent;
20 | import android.view.accessibility.AccessibilityNodeInfo;
21 |
22 | import java.io.IOException;
23 | import java.util.List;
24 |
25 | public class AutoReplyService extends AccessibilityService {
26 | private final static String MM_PNAME = "com.tencent.mm";
27 | boolean hasAction = false;
28 | boolean locked = false;
29 | boolean background = false;
30 | private String name;
31 | private String scontent;
32 | AccessibilityNodeInfo itemNodeinfo;
33 | private KeyguardManager.KeyguardLock kl;
34 | private Handler handler = new Handler();
35 |
36 | /**
37 | * 必须重写的方法,响应各种事件。
38 | * @param event
39 | */
40 | @Override
41 | public void onAccessibilityEvent(final AccessibilityEvent event) {
42 | int eventType = event.getEventType();
43 | android.util.Log.d("maptrix", "get event = " + eventType);
44 | switch (eventType) {
45 | case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:// 通知栏事件
46 | android.util.Log.d("maptrix", "get notification event");
47 | List texts = event.getText();
48 | if (!texts.isEmpty()) {
49 | for (CharSequence text : texts) {
50 | String content = text.toString();
51 | if (!TextUtils.isEmpty(content)) {
52 | if (isScreenLocked()) {
53 | locked = true;
54 | wakeAndUnlock();
55 | android.util.Log.d("maptrix", "the screen is locked");
56 | if (isAppForeground(MM_PNAME)) {
57 | background = false;
58 | android.util.Log.d("maptrix", "is mm in foreground");
59 | sendNotifacationReply(event);
60 | handler.postDelayed(new Runnable() {
61 | @Override
62 | public void run() {
63 | sendNotifacationReply(event);
64 | if (fill()) {
65 | send();
66 | }
67 | }
68 | }, 1000);
69 | } else {
70 | background = true;
71 | android.util.Log.d("maptrix", "is mm in background");
72 | sendNotifacationReply(event);
73 | }
74 | } else {
75 | locked = false;
76 | android.util.Log.d("maptrix", "the screen is unlocked");
77 | if (isAppForeground(MM_PNAME)) {
78 | background = false;
79 | android.util.Log.d("maptrix", "is mm in foreground");
80 | sendNotifacationReply(event);
81 | handler.postDelayed(new Runnable() {
82 | @Override
83 | public void run() {
84 | if (fill()) {
85 | send();
86 | }
87 | }
88 | }, 1000);
89 | } else {
90 | background = true;
91 | android.util.Log.d("maptrix", "is mm in background");
92 | sendNotifacationReply(event);
93 | }
94 | }
95 | }
96 | }
97 | }
98 | break;
99 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
100 | android.util.Log.d("maptrix", "get type window down event");
101 | if (!hasAction) break;
102 | itemNodeinfo = null;
103 | String className = event.getClassName().toString();
104 | if (className.equals("com.tencent.mm.ui.LauncherUI")) {
105 | if (fill()) {
106 | send();
107 | }else {
108 | if(itemNodeinfo != null){
109 | itemNodeinfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
110 | handler.postDelayed(new Runnable() {
111 | @Override
112 | public void run() {
113 | if (fill()) {
114 | send();
115 | }
116 | back2Home();
117 | release();
118 | hasAction = false;
119 | }
120 | }, 1000);
121 | break;
122 | }
123 | }
124 | }
125 | //bring2Front();
126 | back2Home();
127 | release();
128 | hasAction = false;
129 | break;
130 | }
131 | }
132 |
133 | /**
134 | * 寻找窗体中的“发送”按钮,并且点击。
135 | */
136 | @SuppressLint("NewApi")
137 | private void send() {
138 | AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
139 | if (nodeInfo != null) {
140 | List list = nodeInfo
141 | .findAccessibilityNodeInfosByText("发送");
142 | if (list != null && list.size() > 0) {
143 | for (AccessibilityNodeInfo n : list) {
144 | if(n.getClassName().equals("android.widget.Button") && n.isEnabled()){
145 | n.performAction(AccessibilityNodeInfo.ACTION_CLICK);
146 | }
147 | }
148 |
149 | } else {
150 | List liste = nodeInfo
151 | .findAccessibilityNodeInfosByText("Send");
152 | if (liste != null && liste.size() > 0) {
153 | for (AccessibilityNodeInfo n : liste) {
154 | if(n.getClassName().equals("android.widget.Button") && n.isEnabled()){
155 | n.performAction(AccessibilityNodeInfo.ACTION_CLICK);
156 | }
157 | }
158 | }
159 | }
160 | pressBackButton();
161 | }
162 | }
163 | /**
164 | * 模拟back按键
165 | */
166 | private void pressBackButton(){
167 | Runtime runtime = Runtime.getRuntime();
168 | try {
169 | runtime.exec("input keyevent " + KeyEvent.KEYCODE_BACK);
170 | } catch (IOException e) {
171 | e.printStackTrace();
172 | }
173 | }
174 | /**
175 | * 拉起微信界面
176 | * @param event
177 | */
178 | private void sendNotifacationReply(AccessibilityEvent event) {
179 | hasAction = true;
180 | if (event.getParcelableData() != null
181 | && event.getParcelableData() instanceof Notification) {
182 | Notification notification = (Notification) event
183 | .getParcelableData();
184 | String content = notification.tickerText.toString();
185 | String[] cc = content.split(":");
186 | name = cc[0].trim();
187 | scontent = cc[1].trim();
188 |
189 | android.util.Log.i("maptrix", "sender name =" + name);
190 | android.util.Log.i("maptrix", "sender content =" + scontent);
191 |
192 |
193 | PendingIntent pendingIntent = notification.contentIntent;
194 | try {
195 | pendingIntent.send();
196 | } catch (PendingIntent.CanceledException e) {
197 | e.printStackTrace();
198 | }
199 | }
200 | }
201 |
202 | @SuppressLint("NewApi")
203 | private boolean fill() {
204 | AccessibilityNodeInfo rootNode = getRootInActiveWindow();
205 | if (rootNode != null) {
206 | return findEditText(rootNode, "正在忙,稍后回复你");
207 | }
208 | return false;
209 | }
210 |
211 |
212 | private boolean findEditText(AccessibilityNodeInfo rootNode, String content) {
213 | int count = rootNode.getChildCount();
214 |
215 | android.util.Log.d("maptrix", "root class=" + rootNode.getClassName() + ","+ rootNode.getText()+","+count);
216 | for (int i = 0; i < count; i++) {
217 | AccessibilityNodeInfo nodeInfo = rootNode.getChild(i);
218 | if (nodeInfo == null) {
219 | android.util.Log.d("maptrix", "nodeinfo = null");
220 | continue;
221 | }
222 |
223 | android.util.Log.d("maptrix", "class=" + nodeInfo.getClassName());
224 | android.util.Log.e("maptrix", "ds=" + nodeInfo.getContentDescription());
225 | if(nodeInfo.getContentDescription() != null){
226 | int nindex = nodeInfo.getContentDescription().toString().indexOf(name);
227 | int cindex = nodeInfo.getContentDescription().toString().indexOf(scontent);
228 | android.util.Log.e("maptrix", "nindex=" + nindex + " cindex=" +cindex);
229 | if(nindex != -1){
230 | itemNodeinfo = nodeInfo;
231 | android.util.Log.i("maptrix", "find node info");
232 | }
233 | }
234 | if ("android.widget.EditText".equals(nodeInfo.getClassName())) {
235 | android.util.Log.i("maptrix", "==================");
236 | Bundle arguments = new Bundle();
237 | arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
238 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
239 | arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
240 | true);
241 | nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
242 | arguments);
243 | nodeInfo.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
244 | ClipData clip = ClipData.newPlainText("label", content);
245 | ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
246 | clipboardManager.setPrimaryClip(clip);
247 | nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);
248 | return true;
249 | }
250 |
251 | if (findEditText(nodeInfo, content)) {
252 | return true;
253 | }
254 | }
255 |
256 | return false;
257 | }
258 |
259 | @Override
260 | public void onInterrupt() {
261 |
262 | }
263 |
264 | /**
265 | * 判断指定的应用是否在前台运行
266 | *
267 | * @param packageName
268 | * @return
269 | */
270 | private boolean isAppForeground(String packageName) {
271 | ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
272 | ComponentName cn = am.getRunningTasks(1).get(0).topActivity;
273 | String currentPackageName = cn.getPackageName();
274 | if (!TextUtils.isEmpty(currentPackageName) && currentPackageName.equals(packageName)) {
275 | return true;
276 | }
277 |
278 | return false;
279 | }
280 |
281 |
282 | /**
283 | * 将当前应用运行到前台
284 | */
285 | private void bring2Front() {
286 | ActivityManager activtyManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
287 | List runningTaskInfos = activtyManager.getRunningTasks(3);
288 | for (ActivityManager.RunningTaskInfo runningTaskInfo : runningTaskInfos) {
289 | if (this.getPackageName().equals(runningTaskInfo.topActivity.getPackageName())) {
290 | activtyManager.moveTaskToFront(runningTaskInfo.id, ActivityManager.MOVE_TASK_WITH_HOME);
291 | return;
292 | }
293 | }
294 | }
295 |
296 | /**
297 | * 回到系统桌面
298 | */
299 | private void back2Home() {
300 | Intent home = new Intent(Intent.ACTION_MAIN);
301 |
302 | home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
303 | home.addCategory(Intent.CATEGORY_HOME);
304 |
305 | startActivity(home);
306 | }
307 |
308 |
309 | /**
310 | * 系统是否在锁屏状态
311 | *
312 | * @return
313 | */
314 | private boolean isScreenLocked() {
315 | KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
316 | return keyguardManager.inKeyguardRestrictedInputMode();
317 | }
318 |
319 | private void wakeAndUnlock() {
320 | //获取电源管理器对象
321 | PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
322 |
323 | //获取PowerManager.WakeLock对象,后面的参数|表示同时传入两个值,最后的是调试用的Tag
324 | PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright");
325 |
326 | //点亮屏幕
327 | wl.acquire(1000);
328 |
329 | //得到键盘锁管理器对象
330 | KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
331 | kl = km.newKeyguardLock("unLock");
332 |
333 | //解锁
334 | kl.disableKeyguard();
335 |
336 | }
337 |
338 | private void release() {
339 |
340 | if (locked && kl != null) {
341 | android.util.Log.d("maptrix", "release the lock");
342 | //得到键盘锁管理器对象
343 | kl.reenableKeyguard();
344 | locked = false;
345 | }
346 | }
347 | }
348 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ileja/autoreply/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.ileja.autoreply;
2 |
3 | import android.app.KeyguardManager;
4 | import android.content.Context;
5 | import android.os.Bundle;
6 | import android.os.PowerManager;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.view.WindowManager;
9 |
10 | public class MainActivity extends AppCompatActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_main);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsMelo/WcAutoReply/b5d868495e9c032c4fffccffa6fc2e60ebcaad15/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsMelo/WcAutoReply/b5d868495e9c032c4fffccffa6fc2e60ebcaad15/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsMelo/WcAutoReply/b5d868495e9c032c4fffccffa6fc2e60ebcaad15/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsMelo/WcAutoReply/b5d868495e9c032c4fffccffa6fc2e60ebcaad15/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsMelo/WcAutoReply/b5d868495e9c032c4fffccffa6fc2e60ebcaad15/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | WxAutoReply
3 | autoreply_service
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/auto_reply_service_config.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/test/java/com/ileja/autoreply/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.ileja.autoreply;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------