|
@@ -0,0 +1,447 @@
|
|
|
|
+package me.yoqi.app.wxredpacket.services;
|
|
|
|
+
|
|
|
|
+import android.accessibilityservice.AccessibilityService;
|
|
|
|
+import android.accessibilityservice.GestureDescription;
|
|
|
|
+import android.app.Notification;
|
|
|
|
+import android.app.PendingIntent;
|
|
|
|
+import android.content.ComponentName;
|
|
|
|
+import android.content.Intent;
|
|
|
|
+import android.content.SharedPreferences;
|
|
|
|
+import android.content.pm.PackageManager;
|
|
|
|
+import android.graphics.Path;
|
|
|
|
+import android.graphics.Rect;
|
|
|
|
+import android.os.Bundle;
|
|
|
|
+import android.os.Parcelable;
|
|
|
|
+import android.preference.PreferenceManager;
|
|
|
|
+import android.util.DisplayMetrics;
|
|
|
|
+import android.util.Log;
|
|
|
|
+import android.view.accessibility.AccessibilityEvent;
|
|
|
|
+import android.view.accessibility.AccessibilityNodeInfo;
|
|
|
|
+
|
|
|
|
+import java.util.List;
|
|
|
|
+
|
|
|
|
+import me.yoqi.app.wxredpacket.utils.HongbaoSignature;
|
|
|
|
+import me.yoqi.app.wxredpacket.utils.PowerUtil;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * 红包服务,后台监听微信消息,出现红包关键词就打开微信抢红包。
|
|
|
|
+ */
|
|
|
|
+public class HongbaoService extends AccessibilityService implements SharedPreferences.OnSharedPreferenceChangeListener {
|
|
|
|
+ private static final String WECHAT_DETAILS_EN = "Details";
|
|
|
|
+ private static final String WECHAT_DETAILS_CH = "红包详情";
|
|
|
|
+ private static final String WECHAT_BETTER_LUCK_EN = "Better luck next time!";
|
|
|
|
+ private static final String WECHAT_BETTER_LUCK_CH = "手慢了";
|
|
|
|
+ private static final String WECHAT_EXPIRES_CH = "已超过24小时";
|
|
|
|
+ private static final String WECHAT_VIEW_SELF_CH = "查看红包";
|
|
|
|
+ private static final String WECHAT_VIEW_OTHERS_CH = "领取红包";
|
|
|
|
+ private static final String WECHAT_NOTIFICATION_TIP = "[微信红包]";
|
|
|
|
+ private static final String WECHAT_LUCKMONEY_RECEIVE_ACTIVITY = "LuckyMoneyReceiveUI";
|
|
|
|
+ private static final String WECHAT_LUCKMONEY_DETAIL_ACTIVITY = "LuckyMoneyDetailUI";
|
|
|
|
+ private static final String WECHAT_LUCKMONEY_GENERAL_ACTIVITY = "LauncherUI";
|
|
|
|
+ private static final String WECHAT_LUCKMONEY_CHATTING_ACTIVITY = "ChattingUI";
|
|
|
|
+ private String currentActivityName = WECHAT_LUCKMONEY_GENERAL_ACTIVITY;
|
|
|
|
+
|
|
|
|
+ private AccessibilityNodeInfo rootNodeInfo, mReceiveNode, mUnpackNode;
|
|
|
|
+ private boolean mLuckyMoneyPicked, mLuckyMoneyReceived;
|
|
|
|
+ private int mUnpackCount = 0;
|
|
|
|
+ private boolean mMutex = false, mListMutex = false, mChatMutex = false;
|
|
|
|
+ private HongbaoSignature signature = new HongbaoSignature();
|
|
|
|
+
|
|
|
|
+ private PowerUtil powerUtil;
|
|
|
|
+ private SharedPreferences sharedPreferences;
|
|
|
|
+
|
|
|
|
+ * AccessibilityEvent,收到event,过滤红包信息
|
|
|
|
+ *
|
|
|
|
+ * @param event 事件
|
|
|
|
+ */
|
|
|
|
+ @Override
|
|
|
|
+ public void onAccessibilityEvent(AccessibilityEvent event) {
|
|
|
|
+ if (sharedPreferences == null) return;
|
|
|
|
+
|
|
|
|
+ setCurrentActivityName(event);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (!mMutex) {
|
|
|
|
+ if (sharedPreferences.getBoolean("pref_watch_notification", false) && watchNotifications(event))
|
|
|
|
+ return;
|
|
|
|
+ if (sharedPreferences.getBoolean("pref_watch_list", false) && watchList(event)) return;
|
|
|
|
+ mListMutex = false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!mChatMutex) {
|
|
|
|
+ mChatMutex = true;
|
|
|
|
+ if (sharedPreferences.getBoolean("pref_watch_chat", false)) watchChat(event);
|
|
|
|
+ mChatMutex = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @param event
|
|
|
|
+ */
|
|
|
|
+ private void watchChat(AccessibilityEvent event) {
|
|
|
|
+ Log.i("hongbaoservice","watchChat");
|
|
|
|
+
|
|
|
|
+ this.rootNodeInfo = getRootInActiveWindow();
|
|
|
|
+
|
|
|
|
+ if (rootNodeInfo == null) return;
|
|
|
|
+
|
|
|
|
+ mReceiveNode = null;
|
|
|
|
+ mUnpackNode = null;
|
|
|
|
+
|
|
|
|
+ checkNodeInfo(event.getEventType());
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (mLuckyMoneyReceived && !mLuckyMoneyPicked && (mReceiveNode != null)) {
|
|
|
|
+ mMutex = true;
|
|
|
|
+
|
|
|
|
+ mReceiveNode.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
|
|
|
+ mLuckyMoneyReceived = false;
|
|
|
|
+ mLuckyMoneyPicked = true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (mUnpackCount == 1 && (mUnpackNode != null)) {
|
|
|
|
+ int delayFlag = sharedPreferences.getInt("pref_open_delay", 0) * 1000;
|
|
|
|
+ new android.os.Handler().postDelayed(
|
|
|
|
+ new Runnable() {
|
|
|
|
+ public void run() {
|
|
|
|
+ try {
|
|
|
|
+ openPacket();
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ mMutex = false;
|
|
|
|
+ mLuckyMoneyPicked = false;
|
|
|
|
+ mUnpackCount = 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ delayFlag);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * 点击“打开”,收红包
|
|
|
|
+ */
|
|
|
|
+ private void openPacket() {
|
|
|
|
+ Log.i("hongbaoservice","openPacket");
|
|
|
|
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
|
|
|
|
+ float dpi = metrics.density;
|
|
|
|
+ if (android.os.Build.VERSION.SDK_INT <= 23) {
|
|
|
|
+ mUnpackNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
|
|
|
+ } else {
|
|
|
|
+ if (android.os.Build.VERSION.SDK_INT > 23) {
|
|
|
|
+
|
|
|
|
+ Path path = new Path();
|
|
|
|
+ if (640 == dpi) {
|
|
|
|
+ path.moveTo(720, 1575);
|
|
|
|
+ } else {
|
|
|
|
+ path.moveTo(540, 1060);
|
|
|
|
+ }
|
|
|
|
+ GestureDescription.Builder builder = new GestureDescription.Builder();
|
|
|
|
+ GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, 450, 50)).build();
|
|
|
|
+ dispatchGesture(gestureDescription, new GestureResultCallback() {
|
|
|
|
+ @Override
|
|
|
|
+ public void onCompleted(GestureDescription gestureDescription) {
|
|
|
|
+ Log.d("test", "onCompleted");
|
|
|
|
+ mMutex = false;
|
|
|
|
+ super.onCompleted(gestureDescription);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void onCancelled(GestureDescription gestureDescription) {
|
|
|
|
+ Log.d("test", "onCancelled");
|
|
|
|
+ mMutex = false;
|
|
|
|
+ super.onCancelled(gestureDescription);
|
|
|
|
+ }
|
|
|
|
+ }, null);
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ * 返回桌面
|
|
|
|
+ */
|
|
|
|
+ private void back2Home() {
|
|
|
|
+ Intent home=new Intent(Intent.ACTION_MAIN);
|
|
|
|
+ home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
+ home.addCategory(Intent.CATEGORY_HOME);
|
|
|
|
+ startActivity(home);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @param event
|
|
|
|
+ */
|
|
|
|
+ private void setCurrentActivityName(AccessibilityEvent event) {
|
|
|
|
+ if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ ComponentName componentName = new ComponentName(
|
|
|
|
+ event.getPackageName().toString(),
|
|
|
|
+ event.getClassName().toString()
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ getPackageManager().getActivityInfo(componentName, 0);
|
|
|
|
+ currentActivityName = componentName.flattenToShortString();
|
|
|
|
+ } catch (PackageManager.NameNotFoundException e) {
|
|
|
|
+ currentActivityName = WECHAT_LUCKMONEY_GENERAL_ACTIVITY;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @param event
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private boolean watchList(AccessibilityEvent event) {
|
|
|
|
+ Log.i("hongbaoservice","watchList");
|
|
|
|
+ if (mListMutex) return false;
|
|
|
|
+ mListMutex = true;
|
|
|
|
+ AccessibilityNodeInfo eventSource = event.getSource();
|
|
|
|
+
|
|
|
|
+ if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED || eventSource == null)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ List<AccessibilityNodeInfo> nodes = eventSource.findAccessibilityNodeInfosByText(WECHAT_NOTIFICATION_TIP);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (!nodes.isEmpty() && currentActivityName.contains(WECHAT_LUCKMONEY_GENERAL_ACTIVITY)) {
|
|
|
|
+ AccessibilityNodeInfo nodeToClick = nodes.get(0);
|
|
|
|
+ if (nodeToClick == null) return false;
|
|
|
|
+ CharSequence contentDescription = nodeToClick.getContentDescription();
|
|
|
|
+ if (contentDescription != null && !signature.getContentDescription().equals(contentDescription)) {
|
|
|
|
+ nodeToClick.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
|
|
|
+ signature.setContentDescription(contentDescription.toString());
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @param event
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private boolean watchNotifications(AccessibilityEvent event) {
|
|
|
|
+ Log.i("hongbaoservice","watchNotifications");
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (event.getEventType() != AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ String tip = event.getText().toString();
|
|
|
|
+ if (!tip.contains(WECHAT_NOTIFICATION_TIP)) return true;
|
|
|
|
+
|
|
|
|
+ Parcelable parcelable = event.getParcelableData();
|
|
|
|
+ if (parcelable instanceof Notification) {
|
|
|
|
+ Notification notification = (Notification) parcelable;
|
|
|
|
+ try {
|
|
|
|
+
|
|
|
|
+ signature.cleanSignature();
|
|
|
|
+
|
|
|
|
+ notification.contentIntent.send();
|
|
|
|
+ } catch (PendingIntent.CanceledException e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void onInterrupt() {
|
|
|
|
+ Log.d("HongbaoService", "抢红包服务快被终结了");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @param node 输入的根节点
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private AccessibilityNodeInfo findOpenButton(AccessibilityNodeInfo node) {
|
|
|
|
+ if (node == null)
|
|
|
|
+ return null;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (node.getChildCount() == 0) {
|
|
|
|
+ if ("android.widget.Button".equals(node.getClassName()))
|
|
|
|
+ return node;
|
|
|
|
+ else
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ AccessibilityNodeInfo button;
|
|
|
|
+ for (int i = 0; i < node.getChildCount(); i++) {
|
|
|
|
+ button = findOpenButton(node.getChild(i));
|
|
|
|
+ if (button != null)
|
|
|
|
+ return button;
|
|
|
|
+ }
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @param eventType
|
|
|
|
+ */
|
|
|
|
+ private void checkNodeInfo(int eventType) {
|
|
|
|
+ if (this.rootNodeInfo == null) return;
|
|
|
|
+
|
|
|
|
+ if (signature.commentString != null) {
|
|
|
|
+ sendComment();
|
|
|
|
+ signature.commentString = null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ AccessibilityNodeInfo node1 = (sharedPreferences.getBoolean("pref_watch_self", false)) ?
|
|
|
|
+ this.getTheLastNode(WECHAT_VIEW_OTHERS_CH, WECHAT_VIEW_SELF_CH) : this.getTheLastNode(WECHAT_VIEW_OTHERS_CH);
|
|
|
|
+ if (node1 != null &&
|
|
|
|
+ (currentActivityName.contains(WECHAT_LUCKMONEY_CHATTING_ACTIVITY)
|
|
|
|
+ || currentActivityName.contains(WECHAT_LUCKMONEY_GENERAL_ACTIVITY))) {
|
|
|
|
+ String excludeWords = sharedPreferences.getString("pref_watch_exclude_words", "");
|
|
|
|
+ if (this.signature.generateSignature(node1, excludeWords)) {
|
|
|
|
+ mLuckyMoneyReceived = true;
|
|
|
|
+ mReceiveNode = node1;
|
|
|
|
+ Log.d("sig", this.signature.toString());
|
|
|
|
+ }
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ AccessibilityNodeInfo node2 = findOpenButton(this.rootNodeInfo);
|
|
|
|
+ if (node2 != null && "android.widget.Button".equals(node2.getClassName()) && currentActivityName.contains(WECHAT_LUCKMONEY_RECEIVE_ACTIVITY)) {
|
|
|
|
+ mUnpackNode = node2;
|
|
|
|
+ mUnpackCount += 1;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ boolean hasNodes = this.hasOneOfThoseNodes(
|
|
|
|
+ WECHAT_BETTER_LUCK_CH, WECHAT_DETAILS_CH,
|
|
|
|
+ WECHAT_BETTER_LUCK_EN, WECHAT_DETAILS_EN, WECHAT_EXPIRES_CH);
|
|
|
|
+
|
|
|
|
+ if (mMutex && eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && hasNodes
|
|
|
|
+ && (currentActivityName.contains(WECHAT_LUCKMONEY_DETAIL_ACTIVITY)
|
|
|
|
+ || currentActivityName.contains(WECHAT_LUCKMONEY_RECEIVE_ACTIVITY))) {
|
|
|
|
+ mMutex = false;
|
|
|
|
+ mLuckyMoneyPicked = false;
|
|
|
|
+ mUnpackCount = 0;
|
|
|
|
+ performGlobalAction(GLOBAL_ACTION_BACK);
|
|
|
|
+ signature.commentString = generateCommentString();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * 发送回复
|
|
|
|
+ */
|
|
|
|
+ private void sendComment() {
|
|
|
|
+ try {
|
|
|
|
+ AccessibilityNodeInfo outNode =
|
|
|
|
+ getRootInActiveWindow().getChild(0).getChild(0);
|
|
|
|
+ AccessibilityNodeInfo nodeToInput = outNode.getChild(outNode.getChildCount() - 1).getChild(0).getChild(1);
|
|
|
|
+
|
|
|
|
+ if ("android.widget.EditText".equals(nodeToInput.getClassName())) {
|
|
|
|
+ Bundle arguments = new Bundle();
|
|
|
|
+ arguments.putCharSequence(AccessibilityNodeInfo
|
|
|
|
+ .ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, signature.commentString);
|
|
|
|
+ nodeToInput.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
|
|
|
|
+ }
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @param texts
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private boolean hasOneOfThoseNodes(String... texts) {
|
|
|
|
+ List<AccessibilityNodeInfo> nodes;
|
|
|
|
+ for (String text : texts) {
|
|
|
|
+ if (text == null) continue;
|
|
|
|
+
|
|
|
|
+ nodes = this.rootNodeInfo.findAccessibilityNodeInfosByText(text);
|
|
|
|
+
|
|
|
|
+ if (nodes != null && !nodes.isEmpty()) return true;
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @param texts
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private AccessibilityNodeInfo getTheLastNode(String... texts) {
|
|
|
|
+ int bottom = 0;
|
|
|
|
+ AccessibilityNodeInfo lastNode = null, tempNode;
|
|
|
|
+ List<AccessibilityNodeInfo> nodes;
|
|
|
|
+
|
|
|
|
+ for (String text : texts) {
|
|
|
|
+ if (text == null) continue;
|
|
|
|
+
|
|
|
|
+ nodes = this.rootNodeInfo.findAccessibilityNodeInfosByText(text);
|
|
|
|
+
|
|
|
|
+ if (nodes != null && !nodes.isEmpty()) {
|
|
|
|
+ tempNode = nodes.get(nodes.size() - 1);
|
|
|
|
+ if (tempNode == null) return null;
|
|
|
|
+ Rect bounds = new Rect();
|
|
|
|
+ tempNode.getBoundsInScreen(bounds);
|
|
|
|
+ if (bounds.bottom > bottom) {
|
|
|
|
+ bottom = bounds.bottom;
|
|
|
|
+ lastNode = tempNode;
|
|
|
|
+ signature.others = text.equals(WECHAT_VIEW_OTHERS_CH);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return lastNode;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void onServiceConnected() {
|
|
|
|
+ super.onServiceConnected();
|
|
|
|
+ this.watchFlagsFromPreference();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * 如果设置了锁屏抢则,解锁屏幕
|
|
|
|
+ */
|
|
|
|
+ private void watchFlagsFromPreference() {
|
|
|
|
+ sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
+ sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
|
|
|
+
|
|
|
|
+ this.powerUtil = new PowerUtil(this);
|
|
|
|
+ Boolean watchOnLockFlag = sharedPreferences.getBoolean("pref_watch_on_lock", false);
|
|
|
|
+ this.powerUtil.handleWakeLock(watchOnLockFlag);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
|
|
|
+ if (key.equals("pref_watch_on_lock")) {
|
|
|
|
+ Boolean changedValue = sharedPreferences.getBoolean(key, false);
|
|
|
|
+ this.powerUtil.handleWakeLock(changedValue);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void onDestroy() {
|
|
|
|
+ this.powerUtil.handleWakeLock(false);
|
|
|
|
+ super.onDestroy();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ * @return 返回回复字符串
|
|
|
|
+ */
|
|
|
|
+ private String generateCommentString() {
|
|
|
|
+ if (!signature.others) return null;
|
|
|
|
+
|
|
|
|
+ Boolean needComment = sharedPreferences.getBoolean("pref_comment_switch", false);
|
|
|
|
+ if (!needComment) return null;
|
|
|
|
+
|
|
|
|
+ String[] wordsArray = sharedPreferences.getString("pref_comment_words", "").split(" +");
|
|
|
|
+ if (wordsArray.length == 0) return null;
|
|
|
|
+
|
|
|
|
+ Boolean atSender = sharedPreferences.getBoolean("pref_comment_at", false);
|
|
|
|
+ if (atSender) {
|
|
|
|
+ return "@" + signature.sender + " " + wordsArray[(int) (Math.random() * wordsArray.length)];
|
|
|
|
+ } else {
|
|
|
|
+ return wordsArray[(int) (Math.random() * wordsArray.length)];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|