version:2.2.2
bugfixes:优化自动拨号和自动接听 update:
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
28
app/src/main/java/com/vscool/os/utils/ToastUtils.java
Normal file
28
app/src/main/java/com/vscool/os/utils/ToastUtils.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
16
build.gradle
16
build.gradle
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user