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 4dc9695..253c67b 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 @@ -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 nodeInfos = findNodesByViewId(text); + Optional 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 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 findNodesByViewId(String id) { - List accessibilityNodeInfos = getRootInActiveWindow().findAccessibilityNodeInfosByViewId(id); - return accessibilityNodeInfos; + AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); + if (nodeInfo != null) { + List 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() { diff --git a/app/src/main/java/com/vscool/os/base/BaseApplication.java b/app/src/main/java/com/vscool/os/base/BaseApplication.java index 36ff131..c423c2e 100644 --- a/app/src/main/java/com/vscool/os/base/BaseApplication.java +++ b/app/src/main/java/com/vscool/os/base/BaseApplication.java @@ -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(); } diff --git a/app/src/main/java/com/vscool/os/utils/ToastUtils.java b/app/src/main/java/com/vscool/os/utils/ToastUtils.java new file mode 100644 index 0000000..5b09ff2 --- /dev/null +++ b/app/src/main/java/com/vscool/os/utils/ToastUtils.java @@ -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(); + } +} diff --git a/app/src/main/res/xml/accessibility_service_config.xml b/app/src/main/res/xml/accessibility_service_config.xml index 143597b..5f025e4 100644 --- a/app/src/main/res/xml/accessibility_service_config.xml +++ b/app/src/main/res/xml/accessibility_service_config.xml @@ -1,7 +1,7 @@