package com.xxpatx.os.service; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.GestureDescription; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Toast; import com.tencent.mmkv.MMKV; import com.xxpatx.os.bean.Contact; import com.xxpatx.os.config.CommonConfig; /** * 通过微信标签最高支持8.0.49,8.0.50 获取不到数据 */ public class WeAccessibilityService extends AccessibilityService { private static final String TAG = "WeAccessibilityService"; private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE); private static final String DIALER_TEXT = "音视频通话"; private static final String CONTACT_TEXT = "通讯录"; private static final String TAG_TEXT = "标签"; private static final String TAG_NAME = "孝心通"; private static final String MORE_NAME = "更多功能按钮,已折叠"; private static final String PARENT_VIDEO_TEXT = "视频通话"; private static final String VIDEO_TEXT = "视频通话"; private static final String CALL_TEXT = "语音通话"; private static final String RECEIVE_DESCRIPTION = "接听"; public static final int TYPE_VOICE = 0; public static final int TYPE_VIDEO = 1; private static final int WAIT_TIME = 1000; private int mCallType = TYPE_VOICE; private Contact mContact; private Step mCurrentStep = Step.WAITING; private String mName = "";//微信昵称 private String mTagName = "";//微信联系人标签名 private boolean mAutoAccept = false; private boolean finished = true; private Handler handler = null; private AccessibilityEvent input = null; private Runnable runnable = new Runnable() { @Override public void run() { _onAccessibilityEvent(input); finished = true; } }; @Override public void onCreate() { super.onCreate(); Log.e(TAG, "onCreate: "); registerSettingReceiver(); mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_CALL_AUTO_ACCEPT, false); handler = new Handler(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.e(TAG, "onStartCommand: "); if (intent != null) { mContact = (Contact) intent.getSerializableExtra("WechatInfo"); Log.e(TAG, "onStartCommand: wechatInfo = " + mContact); mCallType = intent.getIntExtra("call_type", TYPE_VOICE); mName = mContact.getName(); String groupTag = mContact.getTag(); if (TextUtils.isEmpty(groupTag)) { mTagName = TAG_NAME; } else { mTagName = groupTag; } mCurrentStep = Step.CLICK_CONTACT; launchWeChat(); } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.e(TAG, "onDestroy: "); if (mSettingBroadcastReceiver != null) { unregisterReceiver(mSettingBroadcastReceiver); } } @Override public void onAccessibilityEvent(AccessibilityEvent event) { Log.v(TAG, "onAccessibilityEvent: event = " + event.toString()); if (finished) { finished = false; } else { Log.v(TAG, "bounce"); handler.removeCallbacks(runnable); } input = event; handler.postDelayed(runnable, WAIT_TIME); } /** * 1.在微信页面直接找到联系人拨打电话 * 2.在联系人页面找到并拨打 * 3.通过进入联系人-标签找到并拨打 * * @param event */ private void _onAccessibilityEvent(AccessibilityEvent event) { Log.e(TAG, "_onAccessibilityEvent: " + mCurrentStep); switch (mCurrentStep) { case WAITING: if (!mAutoAccept) return; if (stepAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) { mCurrentStep = Step.WAITING; Toast.makeText(this, "已自动接听视频/语音", Toast.LENGTH_LONG).show(); } break; case CLICK_HOME://主页能找到直接点击进去更多 stepHome(Property.TEXT, mName); break; case CLICK_QUICK_WECHAT_CALL://点击更多页面 step(Property.DESCRIPTION, MORE_NAME, Step.CLICK_TARGET); break; case CLICK_TARGET://点击视频通话 stepCall(Property.TEXT, PARENT_VIDEO_TEXT); break; case CLICK_CONTACT://进入通讯录界面 step(Property.TEXT, CONTACT_TEXT, Step.FIND_TAG); break; case FIND_CONTACT://模拟滑动找到联系人 findContact(Property.TEXT, mName, Step.CLICK_NAME); case FIND_TAG: step(Property.TEXT, TAG_TEXT, Step.CLICK_TAG); break; case CLICK_TAG: step(Property.TEXT, mTagName, Step.CLICK_NAME); break; case CLICK_NAME://点击item step(Property.TEXT, mName, Step.CLICK_INFO); break; case CLICK_INFO://进入个人信息页面 stepCallDialog(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL); break; case CLICK_CALL://打视频或者电话 if (mCallType == TYPE_VIDEO) { step(Property.TEXT, VIDEO_TEXT, Step.WAITING); } else if (mCallType == TYPE_VOICE) { step(Property.TEXT, CALL_TEXT, Step.WAITING); } break; // case CLICK_VIDEO_CALL: // if (step(Property.TEXT, VIDEO_TEXT)) { // Log.d(TAG, "finish, now: " + mCurrentStep); // Toast.makeText(this, "成功发起视频聊天", Toast.LENGTH_LONG).show(); // } // break; default: } } @Override public void onInterrupt() { Log.e(TAG, "onInterrupt: "); } @Override protected void onServiceConnected() { super.onServiceConnected(); Log.e(TAG, "onServiceConnected: "); } private void launchWeChat() { Intent intent = new Intent(); ComponentName cmp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.LauncherUI"); intent.setAction(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.setComponent(cmp); try { startActivity(intent); } catch (Exception e) { Log.e(TAG, "launchWeChat: " + e.getMessage()); } } private boolean step(Property type, String text, Step nextStep) { AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); if (node != null) { clickNode(node); Log.e(TAG, "step: mCurrentStep: " + mCurrentStep + " done"); mCurrentStep = nextStep; Log.e(TAG, "step: next: " + mCurrentStep); return true; } else { return false; } } private boolean stepCallDialog(Property type, String text, Step nextStep) { AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); if (node != null) { Log.e(TAG, "stepCallDialog: isVisibleToUser: " + node.isVisibleToUser()); if (node.isVisibleToUser()) { clickNode(node); Log.e(TAG, "stepCallDialog: mCurrentStep: " + mCurrentStep + " done"); mCurrentStep = nextStep; Log.e(TAG, "stepCallDialog: next: " + mCurrentStep); return true; } else { scrolDown(); return false; } } else { scrolDown(); return false; } } private boolean stepHome(Property type, String text) { AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); if (node != null) { clickNode(node); Log.e(TAG, "stepHome: mCurrentStep: " + mCurrentStep + " done"); mCurrentStep = mCurrentStep.next(); Log.e(TAG, "stepHome: next: " + mCurrentStep); return true; } else { mCurrentStep = Step.CLICK_CONTACT; return false; } } private int mFindCount = 0; private int mMaxCount = 5; private boolean findContact(Property type, String text, Step nextStep) { AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); if (node != null) { clickNode(node); Log.e("findContact", "mCurrentStep: " + mCurrentStep + " done"); mCurrentStep = nextStep; Log.e("findContact", "next: " + mCurrentStep); mFindCount = 0; return true; } else { if (mFindCount == mMaxCount) { Log.e("findContact", "mCurrentStep: max"); Toast.makeText(this, "没有找到联系人", Toast.LENGTH_LONG).show(); mCurrentStep = Step.WAITING; mFindCount = 0; return false; } else { Log.e("findContact", "mCurrentStep: not found"); mFindCount++; Log.e("findContact", "mCurrentStep: mFindCount = " + mFindCount); scrolDown(); return false; } } } private AccessibilityNodeInfo findNode(AccessibilityNodeInfo root, Property type, String text) { if (root == null) return null; // Log.v(TAG, "findNode: getPackageName = " + root.getPackageName()); Log.v(TAG, "findNode: getText = " + root.getText()); Log.v(TAG, "findNode: getClassName = " + root.getClassName()); Log.v(TAG, "findNode: getContentDescription = " + root.getContentDescription()); boolean satisfied = false; switch (type) { case TEXT: satisfied = root.getText() != null && text.contentEquals(root.getText()); break; case CLASS_NAME: satisfied = root.getClassName() != null && text.contentEquals(root.getClassName()); break; case DESCRIPTION: satisfied = root.getContentDescription() != null && text.contentEquals(root.getContentDescription()); break; default: } if (satisfied) { return root; } else { for (int i = 0; i < root.getChildCount(); i++) { AccessibilityNodeInfo result = findNode(root.getChild(i), type, text); if (result != null) { return result; } } } root.recycle(); return null; } private void clickNode(AccessibilityNodeInfo node) { try { Log.e(TAG, "clickNode: getText = " + node.getText()); } catch (Exception e) { Log.e(TAG, "clickNode: e = " + e.getMessage()); } Log.e(TAG, "clickNode: isClickable = " + node.isClickable()); if (node.isClickable()) { boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK); Log.e(TAG, "clickNode: performAction = " + performAction); if (!performAction) { Rect rect = new Rect(); node.getBoundsInScreen(rect); int x = rect.left; int y = rect.top; Log.e(TAG, "clickNode: x = " + x); Log.e(TAG, "clickNode: y = " + y); int width = rect.width(); int height = rect.height(); Log.e(TAG, "clickNode: width = " + width); Log.e(TAG, "clickNode: height = " + height); Log.e(TAG, "clickNode: clickByNode = " + clickByNode(x + width / 2, y + height / 2)); } node.recycle(); } else { AccessibilityNodeInfo parent = node.getParent(); node.recycle(); clickNode(parent); } } private boolean stepCall(Property type, String text) { AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); if (node != null) { Point point = getPointtByNode(node); Log.e(TAG, "stepCall: " + point); clickByNode(point.x, point.y + 30); // clickNode(node); Log.e(TAG, "stepCall: mCurrentStep " + mCurrentStep + " done"); mCurrentStep = Step.CLICK_CALL; Log.e(TAG, "stepCall: next " + mCurrentStep); return true; } else { Log.e(TAG, "stepCall: not found"); return false; } } private boolean stepAnswer(Property type, String text) { AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text); if (node != null) { Point point = getPointtByNode(node); Log.e(TAG, "stepAnswer: " + point); clickByNode(point.x, point.y - 50); clickByNode(point.x, point.y); // clickNode(node); Log.e(TAG, "stepAnswer: mCurrentStep " + mCurrentStep + " done"); mCurrentStep = Step.WAITING; Log.e(TAG, "stepAnswer: next " + mCurrentStep); return true; } else { Log.e(TAG, "stepAnswer: not found"); return false; } } //根据节点信息可获得对应的x,y坐标 static Point getPointtByNode(AccessibilityNodeInfo node) { if (node == null) { return new Point(0, 0); } Rect rect = new Rect(); node.getBoundsInScreen(rect); Point point = new Point(rect.centerX(), rect.centerY()); return point; } //实现对(x,y)坐标进行点击操作。 private boolean clickByNode(int x, int y) { Point point = new Point(x, y); Path path = new Path(); path.moveTo(point.x, point.y); GestureDescription.Builder builder = new GestureDescription.Builder(); builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200)); GestureDescription gesture = builder.build(); boolean isDispatched = dispatchGesture(gesture, new GestureResultCallback() { @Override public void onCompleted(GestureDescription gestureDescription) { super.onCompleted(gestureDescription); Log.e("clickByNode", "onCompleted: "); } @Override public void onCancelled(GestureDescription gestureDescription) { super.onCancelled(gestureDescription); Log.e("clickByNode", "onCompleted: "); } }, null); return isDispatched; } private boolean scrolDown() { WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getRealMetrics(dm); int width = dm.widthPixels; // 屏幕宽度(像素) int height = dm.heightPixels; // 屏幕高度(像素) float density = dm.density; // 屏幕密度(0.75 / 1.0 / 1.5) int densityDpi = dm.densityDpi; // 屏幕密度dpi(120 / 160 / 240) // 屏幕宽度算法:屏幕宽度(像素)/屏幕密度 // int screenWidth = (int) (width / density); // 屏幕宽度(dp) // int screenHeight = (int) (height / density);// 屏幕高度(dp) Log.e(TAG, "scrolDown: screenWidth = " + width); Log.e(TAG, "scrolDown: screenHeight = " + height); int center_X = width / 2; int center_Y = height / 2; Log.e("scrolDown", "center position:" + "(" + center_X + "," + center_Y + ")"); Path path = new Path(); path.moveTo(center_X, (int) (center_Y * 1.5)); //起点坐标。 path.lineTo(center_X, (int) (center_Y * 0.5)); //终点坐标。 GestureDescription.Builder builder = new GestureDescription.Builder(); GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200)).build(); boolean isDispatched = dispatchGesture(gestureDescription, new GestureResultCallback() { @Override public void onCompleted(GestureDescription gestureDescription) { super.onCompleted(gestureDescription); Log.d("scrolDown", "dispatchGesture ScrollUp onCompleted."); path.close(); } @Override public void onCancelled(GestureDescription gestureDescription) { super.onCancelled(gestureDescription); Log.d("scrolDown", "dispatchGesture ScrollUp cancel."); } }, null); return isDispatched; } private enum Step { WAITING, CLICK_HOME, CLICK_QUICK_WECHAT_CALL, CLICK_TARGET, CLICK_CONTACT, FIND_CONTACT, CLICK_TAG, FIND_TAG, CLICK_NAME, CLICK_INFO, CLICK_CALL, CLICK_VIDEO_CALL; private Step next() { return values()[(this.ordinal() + 1) % values().length]; } } private enum Property { TEXT, CLASS_NAME, DESCRIPTION } public static final String SETTING_CALL_TYPE_ACTION = "setting_call_type_action"; public static final String SETTING_AUTOMATIC_ANSWER_ACTION = "setting_automatic_answer_action"; private SettingBroadcastReceiver mSettingBroadcastReceiver; private void registerSettingReceiver() { if (mSettingBroadcastReceiver == null) { mSettingBroadcastReceiver = new SettingBroadcastReceiver(); } IntentFilter filter = new IntentFilter(); filter.addAction(SETTING_CALL_TYPE_ACTION); filter.addAction(SETTING_AUTOMATIC_ANSWER_ACTION); registerReceiver(mSettingBroadcastReceiver, filter); } private class SettingBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.e("SettingBroadcastReceiver", "onReceive: " + action); if (TextUtils.isEmpty(action)) return; switch (action) { case SETTING_CALL_TYPE_ACTION: int callType = intent.getIntExtra("call_type", TYPE_VOICE); mCallType = callType; Log.e("SettingBroadcastReceiver", "onReceive: callType = " + callType); break; case SETTING_AUTOMATIC_ANSWER_ACTION: boolean autoAnswer = intent.getBooleanExtra("auto_answer", false); mAutoAccept = autoAnswer; Log.e("SettingBroadcastReceiver", "onReceive: autoAnswer = " + autoAnswer); break; default: } } } }