Android微信自动回复功能
寫在前面:
最近接到老大的一個需求,要求在手機端攔截微信的通知(Notification),從而獲得聯系人和內容。之后將聯系人和內容發送到我們的硬件產品上,展示出來之后,再將我們想回復內容傳給微信,并且發送給相應聯系人。
老大還提示我需要用AccessibilityService去實現它,當然在此之前我并不知道AccessibilityService是什么鬼,不過沒關系,?just do IT?!
AccessibilityService
AccessibilityService官方文檔(需翻墻)
上面這個鏈接是AccessibilityService的官方文檔,可以翻墻點進去了解下,我再給大家總結一下:
AccessibilityService是Android系統框架提供給安裝在設備上應用的一個可選的導航反饋特性。AccessibilityService 可以替代應用與用戶交流反饋,比如將文本轉化為語音提示,或是用戶的手指懸停在屏幕上一個較重要的區域時的觸摸反饋等。
如果感覺上面的描述比較抽象,沒關系,也許你見過下面這張圖:
輔助功能中的服務
打開你手機的設置--輔助功能中,有很多APP提供的服務,他們都是基于AccessibilityService編寫的,AccessibilityService可以偵聽你的點擊,長按,手勢,通知欄的變化等。并且你可以通過很多種方式找到窗體中的EditText,Button等組件,去填充他們,去點擊他們來幫你實現自動化的功能。
像360助手的自動安裝功能,它就是偵聽著系統安裝的APP,然后找到“安裝”按鈕,實現了自動點擊。微信自動搶紅包功能,實現方式都是如此。
配置AccessibilityService
首先我們在res文件夾下創建xml文件夾,然后創建一個名為auto_reply_service_config的文件,一會我們會在清單文件中引用它。
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服務未來偵聽的行為做了一些配置,比如?typeNotificationStateChanged?和?typeWindowStateChanged?表示我們需要偵聽通知欄的狀態變化和窗體狀態改變。
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*/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() {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() {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() {public void run() {if (fill()) {send();}back2Home();release();hasAction = false;}}, 1000);break;}}}//bring2Front();back2Home();release();hasAction = false;break;}}/*** 尋找窗體中的“發送”按鈕,并且點擊。*/("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();}}}("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;}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對象,后面的參數|表示同時傳入兩個值,最后的是調試用的TagPowerManager.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" /><applicationandroid: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><serviceandroid: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-dataandroid:name="android.accessibilityservice"android:resource="@xml/auto_reply_service_config"/></service></application> </manifest>為了使用某些必要的API,最低API level應該是18
運行程序,打開服務,看看效果如何把~
打開輔助服務
接著用其他手機試著發送給我幾條微信
自動回復微信
可以看到,自動回復功能就實現了。
寫在后面:
代碼沒有給大家詳細講解,不過看注釋應該可以看懂個大概。當微信程序切換到后臺,或者鎖屏(無鎖屏密碼)時,只要有通知出現,都可以實現自動回復。
關于AccessibilityService可以監控的行為非常多,所以我覺得可以實現各種各樣炫酷的功能,不過我并不建議你打開某些流氓軟件的AccessibilityService服務,因為很有可能造成一些安全問題,所以,自己動手寫就安全多了嘛。
github項目地址:
WcAutoReply
原文地址: http://www.jianshu.com/p/f67e950d84f7
總結
以上是生活随笔為你收集整理的Android微信自动回复功能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自动抢红包,自动安装原理之Accessi
- 下一篇: 微信自动回复和自动抢红包实现原理(一):