version:2.2.2

bugfixes:优化自动拨号和自动接听
update:
This commit is contained in:
2026-01-24 09:38:40 +08:00
parent a992e40182
commit c746191304
5 changed files with 183 additions and 43 deletions

View File

@@ -19,14 +19,15 @@ import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.AccessibilityWindowInfo;
import android.widget.Toast;
import com.hjq.toast.Toaster; import com.hjq.toast.Toaster;
import com.tencent.mmkv.MMKV; import com.tencent.mmkv.MMKV;
import com.vscool.os.bean.Contact; import com.vscool.os.bean.Contact;
import com.vscool.os.config.CommonConfig; import com.vscool.os.config.CommonConfig;
import com.vscool.os.utils.ForegroundAppUtil; import com.vscool.os.utils.ForegroundAppUtil;
import com.vscool.os.utils.ToastUtils;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
@@ -70,7 +71,7 @@ public class SelectToSpeakService extends AccessibilityService {
public static final int TYPE_VOICE = 0; public static final int TYPE_VOICE = 0;
public static final int TYPE_VIDEO = 1; public static final int TYPE_VIDEO = 1;
private static final int WAIT_TIME = 1300; private static final int WAIT_TIME = 1600;
private int mCallType = TYPE_VOICE; private int mCallType = TYPE_VOICE;
@@ -198,15 +199,8 @@ public class SelectToSpeakService extends AccessibilityService {
case WAITING: case WAITING:
mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_CALL_AUTO_ACCEPT, false); mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_CALL_AUTO_ACCEPT, false);
Log.e(TAG, "_onAccessibilityEvent: mAutoAccept = " + mAutoAccept); Log.e(TAG, "_onAccessibilityEvent: mAutoAccept = " + mAutoAccept);
if (!mAutoAccept) { if (mAutoAccept) {
return; autoAccept();
}
if (stepAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) {
mCurrentStep = Step.WECHAT_HANDS_FREE;
Toast.makeText(this, "已自动接听视频/语音", Toast.LENGTH_LONG).show();
} else {
mCurrentStep = Step.WAITING;
// clickAnswer();
} }
break; break;
case WECHAT_HANDS_FREE: case WECHAT_HANDS_FREE:
@@ -245,6 +239,13 @@ public class SelectToSpeakService extends AccessibilityService {
stepCall(Property.TEXT, PARENT_VIDEO_TEXT); stepCall(Property.TEXT, PARENT_VIDEO_TEXT);
// clickVideoCall(); // clickVideoCall();
break; 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_CONTACT://进入通讯录界面 case CLICK_CONTACT://进入通讯录界面
if (stepHome(Property.TEXT, CONTACT_TEXT, Step.FIND_TAG)) { if (stepHome(Property.TEXT, CONTACT_TEXT, Step.FIND_TAG)) {
@@ -271,17 +272,11 @@ public class SelectToSpeakService extends AccessibilityService {
case CLICK_INFO://进入个人信息页面 case CLICK_INFO://进入个人信息页面
stepCallDialog(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL); stepCallDialog(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL);
break; 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: // case CLICK_VIDEO_CALL:
// if (step(Property.TEXT, VIDEO_TEXT)) { // if (step(Property.TEXT, VIDEO_TEXT)) {
// Log.d(TAG, "finish, now: " + mCurrentStep); // Log.d(TAG, "finish, now: " + mCurrentStep);
// Toast.makeText(this, "成功发起视频聊天", Toast.LENGTH_LONG).show(); // ToastUtils.show("成功发起视频聊天");
// } // }
// break; // break;
default: default:
@@ -316,6 +311,95 @@ public class SelectToSpeakService extends AccessibilityService {
} }
} }
private void autoAccept() {
if (stepAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) {
mCurrentStep = Step.WECHAT_HANDS_FREE;
ToastUtils.show("已自动接听视频/语音");
} else if (clickNode("com.tencent.mm:id/kfp", false)) {
mCurrentStep = Step.WECHAT_HANDS_FREE;
ToastUtils.show("已自动接听视频/语音");
} else {
mCurrentStep = Step.WAITING;
// clickAnswer();
}
}
/**
* @param text 对应文本
* @param simulate 是否通过坐标模拟点击
* @return
*/
private boolean clickNode(String text, boolean simulate) {
findFloatWindowNode(text);
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId(text);
Optional<AccessibilityNodeInfo> optional = nodeInfos.stream().findAny();
if (optional.isPresent()) {
AccessibilityNodeInfo node = optional.get();
if (node.isClickable()) {
boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
Log.e(TAG, "clickNode: performAction = " + performAction);
node.recycle();
return performAction;
} else {
if (simulate) {
Point point = getPointtByNode(node);
Log.e(TAG, "clickNode: " + point);
clickByPoint(point.x, point.y);
Log.e(TAG, "clickNode: mCurrentStep " + mCurrentStep + " done");
} else {
clickNode(findClickableNode(node));
}
}
return true;
} else {
Log.e(TAG, "clickNode: not found");
return false;
}
}
private AccessibilityNodeInfo findClickableNode(AccessibilityNodeInfo node) {
if (node == null) {
Log.e(TAG, "findClickableNode: node is null");
return null;
}
if (node.isClickable()) {
return node;
} else {
return findClickableNode(node);
}
}
public void findFloatWindowNode(String id) {
List<AccessibilityWindowInfo> windows = getWindows();
for (AccessibilityWindowInfo window : windows) {
// 筛选悬浮窗窗口
if (isFloatingWindow(window)) {
AccessibilityNodeInfo rootNode = window.getRoot();
// 处理悬浮窗节点
traverseNode(rootNode);
rootNode.recycle(); // 释放资源
}
}
}
private boolean isFloatingWindow(AccessibilityWindowInfo window) {
Log.e(TAG, "isFloatingWindow: " + window.getType());
// 根据窗口类型或标题筛选
return window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY;
}
private void traverseNode(AccessibilityNodeInfo node) {
if (node == null) return;
// 提取节点信息如文本、ID等
String text = node.getText() != null ? node.getText().toString() : "";
String id = node.getViewIdResourceName();
// 递归遍历子节点
for (int i = 0; i < node.getChildCount(); i++) {
traverseNode(node.getChild(i));
}
}
@Deprecated @Deprecated
private void clickAnswer() { private void clickAnswer() {
String className = ForegroundAppUtil.getForegroundActivityName(SelectToSpeakService.this); String className = ForegroundAppUtil.getForegroundActivityName(SelectToSpeakService.this);
@@ -325,7 +409,7 @@ public class SelectToSpeakService extends AccessibilityService {
boolean successful = clickByPoint(595, 1376); boolean successful = clickByPoint(595, 1376);
Log.e(TAG, "clickAnswer: " + successful); Log.e(TAG, "clickAnswer: " + successful);
if (successful) { if (successful) {
Toast.makeText(this, "已自动接听视频/语音", Toast.LENGTH_LONG).show(); ToastUtils.show("已自动接听视频/语音");
} }
} else { } else {
Log.e(TAG, "clickAnswer: Not in the answering interface"); Log.e(TAG, "clickAnswer: Not in the answering interface");
@@ -433,13 +517,19 @@ public class SelectToSpeakService extends AccessibilityService {
clickNode(nodeInfo); clickNode(nodeInfo);
mCurrentStep = nextStep; mCurrentStep = nextStep;
} else { } else {
Toaster.show("没有找到搜索按钮"); // Toaster.show("没有找到搜索按钮");
step(Property.DESCRIPTION, SEARCH_TEXT, Step.CLICK_SEARCH);
} }
} }
private List<AccessibilityNodeInfo> findNodesByViewId(String id) { private List<AccessibilityNodeInfo> findNodesByViewId(String id) {
List<AccessibilityNodeInfo> accessibilityNodeInfos = getRootInActiveWindow().findAccessibilityNodeInfosByViewId(id); AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
return accessibilityNodeInfos; if (nodeInfo != null) {
List<AccessibilityNodeInfo> accessibilityNodeInfos = nodeInfo.findAccessibilityNodeInfosByViewId(id);
return accessibilityNodeInfos;
} else {
return new ArrayList<>();
}
} }
private AccessibilityNodeInfo findNodeByText(AccessibilityNodeInfo root, String text) { private AccessibilityNodeInfo findNodeByText(AccessibilityNodeInfo root, String text) {
@@ -478,7 +568,7 @@ public class SelectToSpeakService extends AccessibilityService {
} else { } else {
if (mFindCount == mMaxCount) { if (mFindCount == mMaxCount) {
Log.e("stepCallDialog", "mCurrentStep: max"); Log.e("stepCallDialog", "mCurrentStep: max");
Toast.makeText(this, "没有找到联系人", Toast.LENGTH_LONG).show(); ToastUtils.show("没有找到联系人");
mCurrentStep = Step.WAITING; mCurrentStep = Step.WAITING;
mFindCount = 0; mFindCount = 0;
return false; return false;
@@ -522,7 +612,7 @@ public class SelectToSpeakService extends AccessibilityService {
} else { } else {
if (mFindCount == mMaxCount) { if (mFindCount == mMaxCount) {
Log.e("findContact", "mCurrentStep: max"); Log.e("findContact", "mCurrentStep: max");
Toast.makeText(this, "没有找到联系人", Toast.LENGTH_LONG).show(); ToastUtils.show("没有找到联系人");
mCurrentStep = Step.WAITING; mCurrentStep = Step.WAITING;
mFindCount = 0; mFindCount = 0;
return false; return false;
@@ -583,6 +673,10 @@ public class SelectToSpeakService extends AccessibilityService {
private void clickNode(AccessibilityNodeInfo node) { private void clickNode(AccessibilityNodeInfo node) {
if (node == null) {
Log.e(TAG, "clickNode: node is null");
return;
}
try { try {
Log.e(TAG, "clickNode: getText = " + node.getText()); Log.e(TAG, "clickNode: getText = " + node.getText());
Log.e(TAG, "clickNode: isClickable = " + node.isClickable()); Log.e(TAG, "clickNode: isClickable = " + node.isClickable());
@@ -811,29 +905,30 @@ public class SelectToSpeakService extends AccessibilityService {
WECHAT_HANDS_FREE, WECHAT_HANDS_FREE,
//电话免提 //电话免提
DIALER_HANDS_FREE, DIALER_HANDS_FREE,
//微信主页找用户名
//1-1微信主页找用户名
CLICK_HOME, CLICK_HOME,
//进入搜索界面 //2-2进入搜索界面
CLICK_SEARCH, CLICK_SEARCH,
//是否弹出了联系人列表 //2-3是否弹出了联系人列表
CLICK_SEARCH_CONTACT, CLICK_SEARCH_CONTACT,
//聊天界面+号 //1-2 2-4聊天界面+号
CLICK_QUICK_WECHAT_CALL, CLICK_QUICK_WECHAT_CALL,
//更多里面视频通话 //1-3 2-5更多里面视频通话
CLICK_TARGET, CLICK_TARGET,
/*1-4 语音通话*/
CLICK_CALL,
//主页点击导航栏通讯录 //主页点击导航栏通讯录
CLICK_CONTACT, CLICK_CONTACT,
FIND_CONTACT, FIND_CONTACT,
//通讯录页面点击标签 //通讯录页面点击标签
FIND_TAG, FIND_TAG,
//点击对应的标签名 //点击对应的标签名
CLICK_TAG, CLICK_TAG,
CLICK_NAME, CLICK_NAME,
CLICK_INFO, CLICK_INFO,
CLICK_CALL,
CLICK_VIDEO_CALL; CLICK_VIDEO_CALL;
private Step next() { private Step next() {

View File

@@ -1,6 +1,8 @@
package com.vscool.os.base; package com.vscool.os.base;
import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
@@ -36,9 +38,20 @@ import com.vscool.os.utils.Utils;
public class BaseApplication extends Application { public class BaseApplication extends Application {
private static final String TAG = "BaseApplication"; private static final String TAG = "BaseApplication";
/**
* ViewModel中因为经常旋转导致弱引用为空
*/
@SuppressLint("StaticFieldLeak")
private static Context context;
public static Context getContext() {
return context;
}
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
context = getApplicationContext();
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
catchException(); catchException();
} }

View File

@@ -0,0 +1,28 @@
package com.vscool.os.utils;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import com.vscool.os.base.BaseApplication;
public class ToastUtils {
public static void show(String msg) {
Handler mainHandler = new Handler(Looper.getMainLooper());
new Thread(() -> {
mainHandler.post(() -> {
Toast.makeText(BaseApplication.getContext(), msg, Toast.LENGTH_SHORT).show();
});
}).start();
}
public static void showLong(String msg) {
Handler mainHandler = new Handler(Looper.getMainLooper());
new Thread(() -> {
mainHandler.post(() -> {
Toast.makeText(BaseApplication.getContext(), msg, Toast.LENGTH_LONG).show();
});
}).start();
}
}

View File

@@ -1,7 +1,7 @@
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask|typeViewClicked|typeViewFocused|typeWindowStateChanged|typeWindowsChanged|typeContextClicked|typeWindowContentChanged" android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackAllMask|feedbackGeneric|feedbackSpoken" android:accessibilityFeedbackType="feedbackAllMask"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews" android:accessibilityFlags="flagDefault|flagReportViewIds|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews"
android:canPerformGestures="true" android:canPerformGestures="true"
android:canRetrieveWindowContent="true" android:canRetrieveWindowContent="true"
android:description="@string/accessibility_service_description" android:description="@string/accessibility_service_description"

View File

@@ -4,11 +4,13 @@ buildscript {
repositories { repositories {
google() google()
mavenCentral() // mavenCentral()
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url 'https://developer.huawei.com/repo/' } maven { url 'https://developer.huawei.com/repo/' }
maven { url 'https://maven.aliyun.com/repository/public/' } maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/repository/central/' } maven { url "https://maven.aliyun.com/repository/jcenter" }
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/google' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.4' classpath 'com.android.tools.build:gradle:3.6.4'
@@ -26,11 +28,13 @@ allprojects {
} }
repositories { repositories {
google() google()
mavenCentral() // mavenCentral()
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url 'https://developer.huawei.com/repo/' } maven { url 'https://developer.huawei.com/repo/' }
maven { url 'https://maven.aliyun.com/repository/public/' } maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/repository/central/' } maven { url "https://maven.aliyun.com/repository/jcenter" }
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/google' }
} }
gradle.projectsEvaluated { gradle.projectsEvaluated {
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {