diff --git a/app/build.gradle b/app/build.gradle index 6bf6a32..1b1fc98 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { minSdkVersion 24 targetSdkVersion 29 - versionCode 218 - versionName "2.1.8" + versionCode 219 + versionName "2.1.9" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/com/google/android/accessibility/selecttospeak/SelectToSpeakService.java b/app/src/main/java/com/google/android/accessibility/selecttospeak/SelectToSpeakService.java index 564b32b..232a2e8 100644 --- a/app/src/main/java/com/google/android/accessibility/selecttospeak/SelectToSpeakService.java +++ b/app/src/main/java/com/google/android/accessibility/selecttospeak/SelectToSpeakService.java @@ -10,6 +10,8 @@ import android.content.IntentFilter; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.text.TextUtils; import android.util.DisplayMetrics; @@ -27,6 +29,8 @@ import com.vscool.os.config.CommonConfig; import com.vscool.os.utils.ForegroundAppUtil; import java.util.List; +import java.util.Optional; +import java.util.Random; /** * 通过微信标签最高支持8.0.49,8.0.50 获取不到数据 @@ -37,9 +41,12 @@ public class SelectToSpeakService extends AccessibilityService { private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE); + private static final int ACTION_IME_ENTER_VERSION = 30; + private static final int ACTION_IME_ENTER_ID = 16908372; private static final String DIALER_TEXT = "音视频通话"; private static final String CONTACT_TEXT = "通讯录"; + private static final String SEARCH_TEXT = "搜索"; private static final String TAG_TEXT = "标签"; private static final String TAG_NAME = "亲情桌面"; private static final String MORE_NAME = "更多功能按钮,已折叠"; @@ -49,11 +56,14 @@ public class SelectToSpeakService extends AccessibilityService { private static final String CALL_TEXT = "语音通话"; private static final String RECEIVE_DESCRIPTION = "接听"; + private static final String HANDS_FREE_TEXT = "扬声器已关"; + private static final String DIALER_HANDS_FREE_TEXT = "免提"; + private static final String DIALER_HANDS_FREE_CLOSE_TEXT = "免提,已关闭"; public static final int TYPE_VOICE = 0; public static final int TYPE_VIDEO = 1; - private static final int WAIT_TIME = 1000; + private static final int WAIT_TIME = 1200; private int mCallType = TYPE_VOICE; @@ -98,7 +108,7 @@ public class SelectToSpeakService extends AccessibilityService { } else { mTagName = groupTag; } - mCurrentStep = Step.CLICK_CONTACT; + mCurrentStep = Step.CLICK_HOME; launchWeChat(); } return super.onStartCommand(intent, flags, startId); @@ -144,9 +154,9 @@ public class SelectToSpeakService extends AccessibilityService { if (!TextUtils.isEmpty(currentClassName)) { switch (currentClassName) { case "com.tencent.mm.ui.LauncherUI": - if (mCurrentStep != Step.FIND_CONTACT) { - mCurrentStep = Step.CLICK_CONTACT; - } +// if (mCurrentStep != Step.FIND_CONTACT) { +// mCurrentStep = Step.CLICK_CONTACT; +// } break; case "com.tencent.mm.plugin.account.ui.WelcomeActivity": case "com.tencent.mm.plugin.account.ui.LoginPasswordUI": @@ -180,40 +190,71 @@ public class SelectToSpeakService extends AccessibilityService { return; } if (stepAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) { - mCurrentStep = Step.WAITING; + mCurrentStep = Step.WECHAT_HANDS_FREE; Toast.makeText(this, "已自动接听视频/语音", Toast.LENGTH_LONG).show(); } else { + mCurrentStep = Step.WAITING; // clickAnswer(); } break; + case WECHAT_HANDS_FREE: + handsFree(Property.DESCRIPTION, HANDS_FREE_TEXT); + break; + case DIALER_HANDS_FREE: + if (findHandsFree(Property.DESCRIPTION, DIALER_HANDS_FREE_CLOSE_TEXT)) { + dialerHandsFree(Property.TEXT, DIALER_HANDS_FREE_TEXT); + } else { + mCurrentStep = Step.WAITING; + } + break; case CLICK_HOME://主页能找到直接点击进去更多 - stepHome(Property.TEXT, mName); + if (stepHome(Property.TEXT, mName)) { + Log.e(TAG, "_onAccessibilityEvent: not found contact in home"); + } else { + clickViewById("com.tencent.mm:id/jha", Step.CLICK_SEARCH); +// step(Property.DESCRIPTION, SEARCH_TEXT, Step.CLICK_SEARCH); + } + break; + case CLICK_SEARCH: + putString(mName, Step.CLICK_SEARCH_CONTACT); + break; + case CLICK_SEARCH_CONTACT: + if (findSearchContact(Step.FIND_CONTACT)) { + findSearchContact(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL); + } else { + Toaster.show("没有找到联系人"); + } break; case CLICK_QUICK_WECHAT_CALL://点击更多页面 - step(Property.DESCRIPTION, MORE_NAME, Step.CLICK_TARGET); + clickViewById("com.tencent.mm:id/bjz", Step.CLICK_TARGET); +// step(Property.DESCRIPTION, MORE_NAME, Step.CLICK_TARGET); break; case CLICK_TARGET://点击视频通话 stepCall(Property.TEXT, PARENT_VIDEO_TEXT); +// clickVideoCall(); break; case CLICK_CONTACT://进入通讯录界面 if (stepHome(Property.TEXT, CONTACT_TEXT, Step.FIND_TAG)) { Log.e(TAG, "_onAccessibilityEvent: enter contact"); } else { -// touchContact(); + touchContact(); } break; case FIND_CONTACT://模拟滑动找到联系人 - findContact(Property.TEXT, mName, Step.CLICK_NAME); + findSearchContact(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL); break; case FIND_TAG: step(Property.TEXT, TAG_TEXT, Step.CLICK_TAG); break; case CLICK_TAG: - step(Property.TEXT, mTagName, Step.CLICK_NAME); + if (!step(Property.TEXT, mTagName, Step.CLICK_NAME)) { + Toaster.show("没有找到标签"); + mCurrentStep = Step.WAITING; + } break; case CLICK_NAME://点击item - step(Property.TEXT, mName, Step.CLICK_INFO); + findContact(Property.TEXT, mName, Step.CLICK_INFO); break; case CLICK_INFO://进入个人信息页面 stepCallDialog(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL); @@ -330,6 +371,61 @@ public class SelectToSpeakService extends AccessibilityService { } } + private boolean findSearchContact(Step nextStep) { + List nodeInfos = findNodesByViewId("com.tencent.mm:id/gzf"); + Log.e(TAG, "findSearchContact: " + nodeInfos); + Optional optional = nodeInfos.stream().findAny(); + return optional.isPresent(); + } + + private void findSearchContact(Property type, String text, Step nextStep) { + List nodeInfos = findNodesByViewId("com.tencent.mm:id/odf"); + Log.e(TAG, "findSearchContact: " + nodeInfos); + Optional optional = nodeInfos.stream().findAny(); + if (optional.isPresent()) { + AccessibilityNodeInfo nodeInfo = optional.get(); + clickNode(nodeInfo); + mCurrentStep = nextStep; + } else { + Toaster.show("没有找到联系人"); + mCurrentStep = Step.WAITING; + } + } + + private void putString(String text, Step nextStep) { + List nodeInfos = findNodesByViewId("com.tencent.mm:id/d98"); + Optional optional = nodeInfos.stream().findAny(); + if (optional.isPresent()) { + AccessibilityNodeInfo nodeInfo = optional.get(); + Bundle args = new Bundle(); + args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text); + nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args); + nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS); // 确保焦点在输入框 + + if (Build.VERSION.SDK_INT >= ACTION_IME_ENTER_VERSION) { + //see https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo.AccessibilityAction#ACTION_IME_ENTER + nodeInfo.performAction(ACTION_IME_ENTER_ID); + } + mCurrentStep = nextStep; + } else { + Toaster.show("没有找到搜索框"); + mCurrentStep = Step.WAITING; + } + } + + private void clickViewById(String id, Step nextStep) { + List nodeInfos = findNodesByViewId(id); + Optional optional = nodeInfos.stream().findAny(); + if (optional.isPresent()) { + AccessibilityNodeInfo nodeInfo = optional.get(); + clickNode(nodeInfo); + mCurrentStep = nextStep; + } else { + Toaster.show("没有找到控件"); + mCurrentStep = Step.WAITING; + } + } + private List findNodesByViewId(String id) { List accessibilityNodeInfos = getRootInActiveWindow().findAccessibilityNodeInfosByViewId(id); return accessibilityNodeInfos; @@ -390,11 +486,11 @@ public class SelectToSpeakService extends AccessibilityService { if (node != null) { clickNode(node); Log.e(TAG, "stepHome: mCurrentStep: " + mCurrentStep + " done"); - mCurrentStep = mCurrentStep.next(); + mCurrentStep = Step.CLICK_QUICK_WECHAT_CALL; Log.e(TAG, "stepHome: next: " + mCurrentStep); return true; } else { - mCurrentStep = Step.CLICK_CONTACT; + mCurrentStep = Step.CLICK_SEARCH; return false; } } @@ -483,6 +579,14 @@ public class SelectToSpeakService extends AccessibilityService { Log.e(TAG, "clickNode: e = " + e.getMessage()); } if (node.isClickable()) { + //防检测机制: + //添加随机延迟(避免高频操作) +// handler.postDelayed(new Runnable() { +// @Override +// public void run() { +// +// } +// }, 1000 + new Random().nextInt(100)); boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK); Log.e(TAG, "clickNode: performAction = " + performAction); if (!performAction) { @@ -511,12 +615,13 @@ public class SelectToSpeakService extends AccessibilityService { // } } + @Deprecated 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); - clickByPoint(point.x, point.y + 30); + clickByPoint(point.x, point.y); // clickNode(node); Log.e(TAG, "stepCall: mCurrentStep " + mCurrentStep + " done"); mCurrentStep = Step.CLICK_CALL; @@ -528,6 +633,18 @@ public class SelectToSpeakService extends AccessibilityService { } } + private void clickVideoCall() { + List nodeInfos = findNodesByViewId("com.tencent.mm:id/a12"); + Optional accessibilityNodeInfo = nodeInfos.stream().findAny(); + if (accessibilityNodeInfo.isPresent()) { + AccessibilityNodeInfo nodeInfo = accessibilityNodeInfo.get(); + clickNode(nodeInfo); + mCurrentStep = Step.CLICK_CALL; + } else { + Toaster.show("没有找到通话按钮"); + } + } + private boolean stepAnswer(Property type, String text) { AccessibilityNodeInfo node = findNode(getWindows(), type, text); if (node != null) { @@ -546,6 +663,52 @@ public class SelectToSpeakService extends AccessibilityService { } } + private boolean dialerHandsFree(Property type, String text) { + AccessibilityNodeInfo node = findNode(getWindows(), type, text); + if (node != null) { + Rect rect = new Rect(); + node.getBoundsInScreen(rect); + Log.e(TAG, "dialerHandsFree: rect = " + rect); + clickNode(node); + Log.e(TAG, "dialerHandsFree: mCurrentStep: " + mCurrentStep + " done"); + mCurrentStep = Step.WAITING; + Log.e(TAG, "dialerHandsFree: next: " + mCurrentStep); + return true; + } else { + return false; + } + } + + private boolean findHandsFree(Property type, String text) { + AccessibilityNodeInfo node = findNode(getWindows(), type, text); + if (node != null) { + Log.e(TAG, "findHandsFree: true"); + return true; + } else { + Log.e(TAG, "findHandsFree: false"); + return false; + } + } + + private boolean handsFree(Property type, String text) { + AccessibilityNodeInfo node = findNode(getWindows(), type, text); + if (node != null) { + Point point = getPointtByNode(node); + Log.e(TAG, "handsFree: " + point); + clickByPoint(point.x, point.y - 50); + clickByPoint(point.x, point.y); +// clickNode(node); + Log.e(TAG, "handsFree: mCurrentStep " + mCurrentStep + " done"); + mCurrentStep = Step.WAITING; + Log.e(TAG, "handsFree: next " + mCurrentStep); + return true; + } else { + Log.e(TAG, "handsFree: not found"); + mCurrentStep = Step.WAITING; + return false; + } + } + //根据节点信息可获得对应的x,y坐标 static Point getPointtByNode(AccessibilityNodeInfo node) { if (node == null) { @@ -565,7 +728,9 @@ public class SelectToSpeakService extends AccessibilityService { Path path = new Path(); path.moveTo(point.x, point.y); GestureDescription.Builder builder = new GestureDescription.Builder(); - builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200)); + //防检测机制: + //添加随机延迟(避免高频操作) + builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200 + new Random().nextInt(100))); GestureDescription gesture = builder.build(); boolean dispatched = dispatchGesture(gesture, new GestureResultCallback() { @Override @@ -631,14 +796,28 @@ public class SelectToSpeakService extends AccessibilityService { private enum Step { WAITING, + //微信免提 + WECHAT_HANDS_FREE, + //电话免提 + DIALER_HANDS_FREE, + //微信主页找用户名 CLICK_HOME, + //进入搜索界面 + CLICK_SEARCH, + //是否弹出了联系人列表 + CLICK_SEARCH_CONTACT, + //聊天界面+号 CLICK_QUICK_WECHAT_CALL, + //更多里面视频通话 CLICK_TARGET, - + //主页点击导航栏通讯录 CLICK_CONTACT, + FIND_CONTACT, - CLICK_TAG, + //通讯录页面点击标签 FIND_TAG, + //点击对应的标签名 + CLICK_TAG, CLICK_NAME, CLICK_INFO,