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.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.widget.Toast;
import com.hjq.toast.Toaster;
import com.tencent.mmkv.MMKV;
import com.vscool.os.bean.Contact;
import com.vscool.os.config.CommonConfig;
import com.vscool.os.utils.ForegroundAppUtil;
import com.vscool.os.utils.ToastUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
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_VIDEO = 1;
private static final int WAIT_TIME = 1300;
private static final int WAIT_TIME = 1600;
private int mCallType = TYPE_VOICE;
@@ -198,15 +199,8 @@ public class SelectToSpeakService extends AccessibilityService {
case WAITING:
mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_CALL_AUTO_ACCEPT, false);
Log.e(TAG, "_onAccessibilityEvent: mAutoAccept = " + mAutoAccept);
if (!mAutoAccept) {
return;
}
if (stepAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) {
mCurrentStep = Step.WECHAT_HANDS_FREE;
Toast.makeText(this, "已自动接听视频/语音", Toast.LENGTH_LONG).show();
} else {
mCurrentStep = Step.WAITING;
// clickAnswer();
if (mAutoAccept) {
autoAccept();
}
break;
case WECHAT_HANDS_FREE:
@@ -245,6 +239,13 @@ public class SelectToSpeakService extends AccessibilityService {
stepCall(Property.TEXT, PARENT_VIDEO_TEXT);
// clickVideoCall();
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://进入通讯录界面
if (stepHome(Property.TEXT, CONTACT_TEXT, Step.FIND_TAG)) {
@@ -271,17 +272,11 @@ public class SelectToSpeakService extends AccessibilityService {
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();
// ToastUtils.show("成功发起视频聊天");
// }
// break;
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
private void clickAnswer() {
String className = ForegroundAppUtil.getForegroundActivityName(SelectToSpeakService.this);
@@ -325,7 +409,7 @@ public class SelectToSpeakService extends AccessibilityService {
boolean successful = clickByPoint(595, 1376);
Log.e(TAG, "clickAnswer: " + successful);
if (successful) {
Toast.makeText(this, "已自动接听视频/语音", Toast.LENGTH_LONG).show();
ToastUtils.show("已自动接听视频/语音");
}
} else {
Log.e(TAG, "clickAnswer: Not in the answering interface");
@@ -433,13 +517,19 @@ public class SelectToSpeakService extends AccessibilityService {
clickNode(nodeInfo);
mCurrentStep = nextStep;
} else {
Toaster.show("没有找到搜索按钮");
// Toaster.show("没有找到搜索按钮");
step(Property.DESCRIPTION, SEARCH_TEXT, Step.CLICK_SEARCH);
}
}
private List<AccessibilityNodeInfo> findNodesByViewId(String id) {
List<AccessibilityNodeInfo> accessibilityNodeInfos = getRootInActiveWindow().findAccessibilityNodeInfosByViewId(id);
return accessibilityNodeInfos;
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null) {
List<AccessibilityNodeInfo> accessibilityNodeInfos = nodeInfo.findAccessibilityNodeInfosByViewId(id);
return accessibilityNodeInfos;
} else {
return new ArrayList<>();
}
}
private AccessibilityNodeInfo findNodeByText(AccessibilityNodeInfo root, String text) {
@@ -478,7 +568,7 @@ public class SelectToSpeakService extends AccessibilityService {
} else {
if (mFindCount == mMaxCount) {
Log.e("stepCallDialog", "mCurrentStep: max");
Toast.makeText(this, "没有找到联系人", Toast.LENGTH_LONG).show();
ToastUtils.show("没有找到联系人");
mCurrentStep = Step.WAITING;
mFindCount = 0;
return false;
@@ -522,7 +612,7 @@ public class SelectToSpeakService extends AccessibilityService {
} else {
if (mFindCount == mMaxCount) {
Log.e("findContact", "mCurrentStep: max");
Toast.makeText(this, "没有找到联系人", Toast.LENGTH_LONG).show();
ToastUtils.show("没有找到联系人");
mCurrentStep = Step.WAITING;
mFindCount = 0;
return false;
@@ -583,6 +673,10 @@ public class SelectToSpeakService extends AccessibilityService {
private void clickNode(AccessibilityNodeInfo node) {
if (node == null) {
Log.e(TAG, "clickNode: node is null");
return;
}
try {
Log.e(TAG, "clickNode: getText = " + node.getText());
Log.e(TAG, "clickNode: isClickable = " + node.isClickable());
@@ -811,29 +905,30 @@ public class SelectToSpeakService extends AccessibilityService {
WECHAT_HANDS_FREE,
//电话免提
DIALER_HANDS_FREE,
//微信主页找用户名
//1-1微信主页找用户名
CLICK_HOME,
//进入搜索界面
//2-2进入搜索界面
CLICK_SEARCH,
//是否弹出了联系人列表
//2-3是否弹出了联系人列表
CLICK_SEARCH_CONTACT,
//聊天界面+号
//1-2 2-4聊天界面+号
CLICK_QUICK_WECHAT_CALL,
//更多里面视频通话
//1-3 2-5更多里面视频通话
CLICK_TARGET,
/*1-4 语音通话*/
CLICK_CALL,
//主页点击导航栏通讯录
CLICK_CONTACT,
FIND_CONTACT,
//通讯录页面点击标签
FIND_TAG,
//点击对应的标签名
CLICK_TAG,
CLICK_NAME,
CLICK_INFO,
CLICK_CALL,
CLICK_VIDEO_CALL;
private Step next() {

View File

@@ -1,6 +1,8 @@
package com.vscool.os.base;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
@@ -36,9 +38,20 @@ import com.vscool.os.utils.Utils;
public class BaseApplication extends Application {
private static final String TAG = "BaseApplication";
/**
* ViewModel中因为经常旋转导致弱引用为空
*/
@SuppressLint("StaticFieldLeak")
private static Context context;
public static Context getContext() {
return context;
}
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
if (!BuildConfig.DEBUG) {
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"
android:accessibilityEventTypes="typeAllMask|typeViewClicked|typeViewFocused|typeWindowStateChanged|typeWindowsChanged|typeContextClicked|typeWindowContentChanged"
android:accessibilityFeedbackType="feedbackAllMask|feedbackGeneric|feedbackSpoken"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackAllMask"
android:accessibilityFlags="flagDefault|flagReportViewIds|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_service_description"

View File

@@ -4,11 +4,13 @@ buildscript {
repositories {
google()
mavenCentral()
// mavenCentral()
maven { url "https://jitpack.io" }
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 {
classpath 'com.android.tools.build:gradle:3.6.4'
@@ -26,11 +28,13 @@ allprojects {
}
repositories {
google()
mavenCentral()
// mavenCentral()
maven { url "https://jitpack.io" }
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 {
tasks.withType(JavaCompile) {