原创

Android微信自动回复功能

写在前面

最近接到老大的一个需求,要求在手机端拦截微信的通知(Notification),从而获得联系人和内容。之后将联系人和内容发送到我们的硬件产品上,展示出来之后,再将我们想回复内容传给微信,并且发送给相应联系人。

老大还提示我需要用AccessibilityService去实现它,当然在此之前我并不知道AccessibilityService是什么鬼,不过没关系, just do IT

AccessibilityService

AccessibilityService官方文档(需科学上网)

上面这个链接是AccessibilityService的官方文档,可以翻墙点进去了解下,我再给大家总结一下:

AccessibilityService是Android系统框架提供给安装在设备上应用的一个可选的导航反馈特性。AccessibilityService 可以替代应用与用户交流反馈,比如将文本转化为语音提示,或是用户的手指悬停在屏幕上一个较重要的区域时的触摸反馈等。

如果感觉上面的描述比较抽象,没关系,也许你见过下面这张图:

Android微信自动回复功能

辅助功能中的服务

打开你手机的设置--辅助功能中,有很多APP提供的服务,他们都是基于AccessibilityService编写的,AccessibilityService可以侦听你的点击,长按,手势,通知栏的变化等。并且你可以通过很多种方式找到窗体中的EditText,Button等组件,去填充他们,去点击他们来帮你实现自动化的功能。

像360助手的自动安装功能,它就是侦听着系统安装的APP,然后找到“安装”按钮,实现了自动点击。微信自动抢红包功能,实现方式都是如此。

配置AccessibilityService

首先我们在res文件夹下创建xml文件夹,然后创建一个名为auto_reply_service_config的文件,一会我们会在清单文件中引用它。

Android微信自动回复功能

AccessibilityService配置文件

代码:

 <accessibility-service               xmlns:android="http://schemas.android.com/apk/res/android"     android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged"     android:accessibilityFeedbackType="feedbackGeneric"     android:accessibilityFlags="flagDefault"     android:canRetrieveWindowContent="true"     android:description="@string/accessibility_description"     android:notificationTimeout="100"     android:packageNames="com.tencent.mm" />

这个文件表示我们对AccessibilityService服务未来侦听的行为做了一些配置,比如 typeNotificationStateChangedtypeWindowStateChanged 表示我们需要侦听通知栏的状态变化和窗体状态改变。
android:packageNames="com.tencent.mm" 这是微信的包名,表示我们只关心微信这一个应用。

代码不打算带着大家一行一行看了,如果有不明白的,去看看文档,或者下面回复我,我给大家解答~

创建AccessibilityService

下面贴出AccessibilityService类的全部代码,注释还算详尽,如有疑问,下方回复。

 package com.ileja.autoreply;  import android.accessibilityservice.AccessibilityService; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; import android.text.TextUtils; import android.view.KeyEvent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo;  import java.io.IOException; import java.util.List;  public class AutoReplyService extends AccessibilityService {     private final static String MM_PNAME = "com.tencent.mm";     boolean hasAction = false;     boolean locked = false;     boolean background = false;     private String name;     private String scontent;     AccessibilityNodeInfo itemNodeinfo;     private KeyguardManager.KeyguardLock kl;     private Handler handler = new Handler();       /**      * 必须重写的方法,响应各种事件。      * @param event      */     @Override     public void onAccessibilityEvent(final AccessibilityEvent event) {         int eventType = event.getEventType();         android.util.Log.d("maptrix", "get event = " + eventType);         switch (eventType) {             case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:// 通知栏事件                 android.util.Log.d("maptrix", "get notification event");                 List<CharSequence> texts = event.getText();                 if (!texts.isEmpty()) {                     for (CharSequence text : texts) {                         String content = text.toString();                         if (!TextUtils.isEmpty(content)) {                             if (isScreenLocked()) {                                 locked = true;                                 wakeAndUnlock();                                 android.util.Log.d("maptrix", "the screen is locked");                                 if (isAppForeground(MM_PNAME)) {                                     background = false;                                     android.util.Log.d("maptrix", "is mm in foreground");                                     sendNotifacationReply(event);                                     handler.postDelayed(new Runnable() {                                         @Override                                         public void run() {                                             sendNotifacationReply(event);                                             if (fill()) {                                                 send();                                             }                                         }                                     }, 1000);                                 } else {                                     background = true;                                     android.util.Log.d("maptrix", "is mm in background");                                     sendNotifacationReply(event);                                 }                             } else {                                 locked = false;                                 android.util.Log.d("maptrix", "the screen is unlocked");                                 // 监听到微信红包的notification,打开通知                                 if (isAppForeground(MM_PNAME)) {                                     background = false;                                     android.util.Log.d("maptrix", "is mm in foreground");                                     sendNotifacationReply(event);                                     handler.postDelayed(new Runnable() {                                         @Override                                         public void run() {                                             if (fill()) {                                                 send();                                             }                                         }                                     }, 1000);                                 } else {                                     background = true;                                     android.util.Log.d("maptrix", "is mm in background");                                     sendNotifacationReply(event);                                 }                             }                         }                     }                 }                 break;             case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:                 android.util.Log.d("maptrix", "get type window down event");                 if (!hasAction) break;                 itemNodeinfo = null;                 String className = event.getClassName().toString();                 if (className.equals("com.tencent.mm.ui.LauncherUI")) {                     if (fill()) {                         send();                     }else {                         if(itemNodeinfo != null){                             itemNodeinfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);                             handler.postDelayed(new Runnable() {                                 @Override                                 public void run() {                                     if (fill()) {                                         send();                                     }                                     back2Home();                                     release();                                     hasAction = false;                                 }                             }, 1000);                             break;                         }                     }                 }                  //bring2Front();                 back2Home();                 release();                 hasAction = false;                 break;         }     }      /**      * 寻找窗体中的“发送”按钮,并且点击。      */     @SuppressLint("NewApi")     private void send() {         AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();         if (nodeInfo != null) {             List<AccessibilityNodeInfo> list = nodeInfo                     .findAccessibilityNodeInfosByText("发送");             if (list != null && list.size() > 0) {                 for (AccessibilityNodeInfo n : list) {                     if(n.getClassName().equals("android.widget.Button") && n.isEnabled())                     {                             n.performAction(AccessibilityNodeInfo.ACTION_CLICK);}                     }              } else {                 List<AccessibilityNodeInfo> liste = nodeInfo                         .findAccessibilityNodeInfosByText("Send");                 if (liste != null && liste.size() > 0) {                     for (AccessibilityNodeInfo n : liste) {                         if(n.getClassName().equals("android.widget.Button") && n.isEnabled())                         {                                  n.performAction(AccessibilityNodeInfo.ACTION_CLICK);}                         }                     }                 }             }             pressBackButton();         }      }      /**      * 模拟back按键      */     private void pressBackButton(){         Runtime runtime = Runtime.getRuntime();         try {             runtime.exec("input keyevent " + KeyEvent.KEYCODE_BACK);         } catch (IOException e) {             e.printStackTrace();         }     }      /**      *      * @param event      */     private void sendNotifacationReply(AccessibilityEvent event) {         hasAction = true;         if (event.getParcelableData() != null                 && event.getParcelableData() instanceof Notification) {             Notification notification = (Notification) event                     .getParcelableData();             String content = notification.tickerText.toString();             String[] cc = content.split(":");             name = cc[0].trim();             scontent = cc[1].trim();              android.util.Log.i("maptrix", "sender name =" + name);             android.util.Log.i("maptrix", "sender content =" + scontent);               PendingIntent pendingIntent = notification.contentIntent;             try {                 pendingIntent.send();             } catch (PendingIntent.CanceledException e) {                 e.printStackTrace();             }         }     }      @SuppressLint("NewApi")     private boolean fill() {         AccessibilityNodeInfo rootNode = getRootInActiveWindow();         if (rootNode != null) {             return findEditText(rootNode, "正在忙,稍后回复你");         }         return false;     }       private boolean findEditText(AccessibilityNodeInfo rootNode, String content) {         int count = rootNode.getChildCount();          android.util.Log.d("maptrix", "root class=" + rootNode.getClassName() + ","+ rootNode.getText()+","+count);         for (int i = 0; i < count; i++) {             AccessibilityNodeInfo nodeInfo = rootNode.getChild(i);             if (nodeInfo == null) {                 android.util.Log.d("maptrix", "nodeinfo = null");                 continue;             }              android.util.Log.d("maptrix", "class=" + nodeInfo.getClassName());             android.util.Log.e("maptrix", "ds=" + nodeInfo.getContentDescription());             if(nodeInfo.getContentDescription() != null){                 int nindex = nodeInfo.getContentDescription().toString().indexOf(name);                 int cindex = nodeInfo.getContentDescription().toString().indexOf(scontent);                 android.util.Log.e("maptrix", "nindex=" + nindex + " cindex=" +cindex);                 if(nindex != -1){                     itemNodeinfo = nodeInfo;                     android.util.Log.i("maptrix", "find node info");                 }             }             if ("android.widget.EditText".equals(nodeInfo.getClassName())) {                 android.util.Log.i("maptrix", "==================");                 Bundle arguments = new Bundle();                 arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,                         AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);                 arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,                         true);                 nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,                         arguments);                 nodeInfo.performAction(AccessibilityNodeInfo.ACTION_FOCUS);                 ClipData clip = ClipData.newPlainText("label", content);                 ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);                 clipboardManager.setPrimaryClip(clip);                 nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);                 return true;             }              if (findEditText(nodeInfo, content)) {                 return true;             }         }          return false;     }      @Override     public void onInterrupt() {      }      /**      * 判断指定的应用是否在前台运行      *      * @param packageName      * @return      */     private boolean isAppForeground(String packageName) {         ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);         ComponentName cn = am.getRunningTasks(1).get(0).topActivity;         String currentPackageName = cn.getPackageName();         if (!TextUtils.isEmpty(currentPackageName) && currentPackageName.equals(packageName)) {             return true;         }          return false;     }       /**      * 将当前应用运行到前台      */     private void bring2Front() {         ActivityManager activtyManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);         List<ActivityManager.RunningTaskInfo> runningTaskInfos = activtyManager.getRunningTasks(3);         for (ActivityManager.RunningTaskInfo runningTaskInfo : runningTaskInfos) {             if (this.getPackageName().equals(runningTaskInfo.topActivity.getPackageName())) {                 activtyManager.moveTaskToFront(runningTaskInfo.id, ActivityManager.MOVE_TASK_WITH_HOME);                 return;             }         }     }      /**      * 回到系统桌面      */     private void back2Home() {         Intent home = new Intent(Intent.ACTION_MAIN);          home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);         home.addCategory(Intent.CATEGORY_HOME);          startActivity(home);     }       /**      * 系统是否在锁屏状态      *      * @return      */     private boolean isScreenLocked() {         KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);         return keyguardManager.inKeyguardRestrictedInputMode();     }      private void wakeAndUnlock() {         //获取电源管理器对象         PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);          //获取PowerManager.WakeLock对象,后面的参数|表示同时传入两个值,最后的是调试用的Tag         PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright");          //点亮屏幕         wl.acquire(1000);          //得到键盘锁管理器对象         KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);         kl = km.newKeyguardLock("unLock");          //解锁         kl.disableKeyguard();      }      private void release() {          if (locked && kl != null) {             android.util.Log.d("maptrix", "release the lock");             //得到键盘锁管理器对象             kl.reenableKeyguard();             locked = false;         }     } }

接着配置清单文件,权限和service的配置比较重要。

 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"     package="com.ileja.autoreply">      <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />     <uses-permission android:name="android.permission.INTERNET" />     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />     <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />     <uses-permission android:name="android.permission.GET_TASKS" />     <uses-permission android:name="android.permission.REORDER_TASKS" />     <uses-permission android:name="android.permission.WAKE_LOCK" />      <application         android:allowBackup="true"         android:icon="@mipmap/ic_launcher"         android:label="@string/app_name"         android:supportsRtl="true"         android:theme="@style/AppTheme">         <activity android:name=".MainActivity">             <intent-filter>                 <action android:name="android.intent.action.MAIN" />                  <category android:name="android.intent.category.LAUNCHER" />             </intent-filter>         </activity>          <service             android:name=".AutoReplyService"             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/auto_reply_service_config"/>         </service>     </application> </manifest>

为了使用某些必要的API,最低API level应该是18

运行程序,打开服务,看看效果如何把~

Android微信自动回复功能

打开辅助服务

接着用其他手机试着发送给我几条微信

Android微信自动回复功能

自动回复微信

可以看到,自动回复功能就实现了。

写在后面

代码没有给大家详细讲解,不过看注释应该可以看懂个大概。当微信程序切换到后台,或者锁屏(无锁屏密码)时,只要有通知出现,都可以实现自动回复。

关于AccessibilityService可以监控的行为非常多,所以我觉得可以实现各种各样炫酷的功能,不过我并不建议你打开某些流氓软件的AccessibilityService服务,因为很有可能造成一些安全问题,所以,自己动手写就安全多了嘛。


阅读原文

正文到此结束
Loading...