close
当前位置: 物联网在线 > 技术文库 > android >

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,"+ 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; } } }
(责任编辑:ioter)

用户喜欢...

Android Weekly #276 安卓开发周刊 中文版

您是否了解过Android的Lifecycle-Aware库?(android.jlelse.eu) 我们如何了解Lifecycle-Aware库代码? Nishant Srivastava展示了可以跟踪活动或Lifecycle-Aware的Lifecycle Arch组件的片段,并相应地调整其行为。 为Mos...


Android Weekly #275 安卓开发周刊 中文版

MapMe — Android地图适配器 (medium.com) Josh Burton介绍MapMe,是一个用Kotlin编写的Android库,可以将适配器模式带到地图上。 赞助 CloudRail - 连接到API 10x更快 (cloudrail.com) 当我们用单一的界面连接到所...


使用Android Studio开发可独立运行(runnable)混淆过的Jar程序

之前开发Java程序一直都是使用Eclipse 开发Jar程序,现在开发基本上都已经弃用Eclipse了,但是有时偶尔开发个小的Jar程序,还要切换回去好麻烦,刚好前几天有人问几个相关的问题,就顺便整...


Android Weekly #274 安卓开发周刊 中文版

探索Android Oreo上的别后执行限制(medium.com) 在这篇文章中,Joe Birch解释了关于Android Oreo在后台运行服务的变化。 non-Time领主的time – 第5部分 (blog.stylingandroid.com) Mark Allison继续分析JSR 310 date和...


Android Weekly #273 安卓开发周刊 中文版

开源你的Android代码(android.jlelse.eu) 通过您的开源Android代码,您将(希望地)为Android社区提供有价值的代码,收到建设性的反馈,并与您最初建立的内容进行协作从而使您的代码变得更好。这...


Android Weekly #272 安卓开发周刊 中文版

Android Dev 101:每个初学者都应该知道的一些做法() 看一些初学者或媒介等级开发人员(不要错过任何人)应该知道的一些做法,以便更好地摆脱Android框架。 99.9% crash free sessions (medium.com) Chr...


Android Weekly #271 安卓开发周刊 中文版

依赖注入检查(medium.com) 在本文中,MihályNagy引入了依赖注入检查,一种开源注释处理器,可帮助您解决一些出现在所有JSR 330 DI库中常见的问题。 使用Android Studio插件提高效率 (blog.mindorks.com...


Android Weekly #270 安卓开发周刊 中文版

带有RxJava2的SOLID Android分析 (medium.com) 在这篇文章中,Aris Papadopoulos将解释如何正确创建一个分析系统,同时遵循SOLID原则,并使用RxJava2来解决问题。 (blog.stylingandroid.com) Java中的编程时间很难...


Android内存泄漏思考

Android内存泄漏是一个经常要遇到的问题,程序在内存泄漏的时候很容易导致OOM的发生。那么如何查找内存泄漏和避免内存泄漏就是需要知晓的一个问题,首先我们需要知道一些基础知识。...


Android Weekly #269 安卓开发周刊 中文版

在Google上快速提出操作 () Wolfram Rittmeyer分享了开始在Google上快速创建操作所需的所有信息(为了家庭与助理)。 RxJava中的错误处理(rongi.github.io) 一旦开始编写RxJava代码,你就会意识到有些事...