From 732454522fc386fbedbe998b94964ed91fc06b46 Mon Sep 17 00:00:00 2001 From: gallonyin Date: Thu, 11 Aug 2022 18:01:16 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E5=85=A8=E9=9D=A2=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E6=9C=80=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC(4.0.10)=E5=92=8C=E6=94=BF=E5=8A=A1=E5=BE=AE=E4=BF=A1;?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E6=90=9C=E7=B4=A2=E4=BC=98=E5=8C=96;?= =?UTF-8?q?=E5=B7=B2=E7=9F=A5=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 3 + .../java/org/yameida/worktool/Constant.kt | 4 +- .../main/java/org/yameida/worktool/Demo.kt | 5 +- .../worktool/activity/ListenActivity.kt | 26 +++- .../yameida/worktool/service/GlobalMethod.kt | 13 +- .../yameida/worktool/service/WeworkGetImpl.kt | 3 +- .../worktool/service/WeworkLoopImpl.kt | 12 +- .../worktool/service/WeworkOperationImpl.kt | 45 +++--- .../yameida/worktool/service/WeworkService.kt | 14 +- .../worktool/utils/AccessibilityUtil.kt | 135 ++++++++++++------ .../org/yameida/worktool/utils/UpdateUtil.kt | 6 - .../yameida/worktool/utils/WeworkRoomUtil.kt | 74 +++++----- .../yameida/worktool/utils/WeworkTextUtil.kt | 35 +++-- app/src/main/res/layout/activity_listen.xml | 75 +++++++++- 15 files changed, 305 insertions(+), 149 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0610d49..301e65f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.yameida.worktool" minSdkVersion 23 targetSdkVersion 30 - versionCode 1 - versionName "1.3" + versionCode 2 + versionName "2.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1774b6a..ed17bb0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -17,6 +18,8 @@ + LogUtils.i("sw_encrypt onCheckedChanged: $isChecked") Constant.encryptType = if (isChecked) 1 else 0 SPUtils.getInstance().put("encryptType", Constant.encryptType) }) + sw_auto_reply.isChecked = Constant.autoReply == 1 + sw_auto_reply.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked -> + LogUtils.i("sw_auto_reply onCheckedChanged: $isChecked") + Constant.autoReply = if (isChecked) 1 else 0 + SPUtils.getInstance().put("autoReply", Constant.autoReply) + }) tv_host.text = WebConfig.HOST tv_version.text = AppUtils.getAppVersionName() + val workVersionName = AppUtils.getAppInfo(Constant.PACKAGE_NAMES)?.versionName + when (workVersionName) { + null -> { + LogUtils.e("系统检测到您尚未安装企业微信,请先安装企业微信") + tv_work_version.text = "检测到您尚未安装企业微信,请先安装登录!" + } + in Constant.AVAILABLE_VERSION -> { + LogUtils.i("当前企业微信版本已适配: $workVersionName") + val tip = "$workVersionName 已适配,可放心使用~" + tv_work_version.text = tip + } + else -> { + LogUtils.e("当前企业微信版本未兼容: $workVersionName") + val tip = "$workVersionName 可能存在部分兼容性问题!" + tv_work_version.text = tip + } + } + SPUtils.getInstance().put("appVersion", AppUtils.getAppVersionName()) + SPUtils.getInstance().put("workVersion", workVersionName) } private fun initAccessibility() { diff --git a/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt b/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt index 3730952..fd48866 100644 --- a/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt +++ b/app/src/main/java/org/yameida/worktool/service/GlobalMethod.kt @@ -30,7 +30,7 @@ fun goHomeTab(title: String): Boolean { var atHome = false var find = false while (!atHome) { - val list = AccessibilityUtil.findAllByText(getRoot(), "消息", timeout = 0) + val list = AccessibilityUtil.findAllOnceByText(getRoot(), "消息", exact = true) for (item in list) { if (item.parent.parent.parent.childCount == 5) { //处理侧边栏抽屉打开 @@ -42,7 +42,7 @@ fun goHomeTab(title: String): Boolean { } } atHome = true - val tempList = AccessibilityUtil.findAllByText(getRoot(), title, timeout = 0) + val tempList = AccessibilityUtil.findAllOnceByText(getRoot(), title, exact = true) for (tempItem in tempList) { if (tempItem.parent.parent.parent.childCount == 5) { AccessibilityUtil.performClick(tempItem) @@ -64,7 +64,7 @@ fun goHomeTab(title: String): Boolean { * 当前是否在首页 */ fun isAtHome(): Boolean { - val list = AccessibilityUtil.findAllByText(getRoot(), "消息", timeout = 0) + val list = AccessibilityUtil.findAllOnceByText(getRoot(), "消息", exact = true) return list.count { it.parent.parent.parent.childCount == 5 } > 0 } @@ -85,8 +85,7 @@ fun getRoot(ignoreCheck: Boolean): AccessibilityNodeInfo { val root = WeworkController.weworkService.rootInActiveWindow if (tempRoot != root) { LogUtils.e("tempRoot != root") - } - if (root != null) { + } else if (root != null) { if (root.packageName == Constant.PACKAGE_NAMES) { return root } else { @@ -124,9 +123,7 @@ fun backPress() { AccessibilityUtil.performClick(button) } else { LogUtils.d("未找到BT按钮") - val confirm = AccessibilityUtil.findOnceByText(getRoot(), "确定") - ?: AccessibilityUtil.findOnceByText(getRoot(), "我知道了") - ?: AccessibilityUtil.findOnceByText(getRoot(), "暂不进入") + val confirm = AccessibilityUtil.findOnceByText(getRoot(), "确定", "我知道了", "暂不进入", "不用了", "取消") if (confirm != null) { LogUtils.d("尝试点击确定/我知道了/暂不进入") AccessibilityUtil.performClick(confirm) diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt b/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt index c202ed2..d1e69dd 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkGetImpl.kt @@ -1,5 +1,6 @@ package org.yameida.worktool.service +import com.blankj.utilcode.util.GsonUtils import com.blankj.utilcode.util.LogUtils import org.yameida.worktool.Constant import org.yameida.worktool.model.WeworkMessageBean @@ -104,7 +105,7 @@ object WeworkGetImpl { } } } - LogUtils.d("我的信息", myInfo) + LogUtils.d("我的信息", GsonUtils.toJson(myInfo)) val weworkMessageBean = WeworkMessageBean() weworkMessageBean.type = WeworkMessageBean.GET_MY_INFO weworkMessageBean.myInfo = myInfo diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt b/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt index c476fa2..3af2ef1 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt @@ -26,7 +26,7 @@ object WeworkLoopImpl { mainLoopRunning = true try { while (mainLoopRunning) { - if (WeworkRoomUtil.getRoomType(getRoot(), false) != WeworkMessageBean.ROOM_TYPE_UNKNOWN + if (WeworkRoomUtil.getRoomType(false) != WeworkMessageBean.ROOM_TYPE_UNKNOWN && getChatMessageList()) { } if (!mainLoopRunning) break @@ -49,7 +49,7 @@ object WeworkLoopImpl { * 读取通讯录好友请求 */ fun getFriendRequest(): Boolean { - val list = AccessibilityUtil.findAllByText(getRoot(), "通讯录", timeout = 0) + val list = AccessibilityUtil.findAllOnceByText(getRoot(), "通讯录", exact = true) for (item in list) { if (item.parent.parent.parent.childCount == 5) { if (item.parent.childCount > 1) { @@ -94,9 +94,10 @@ object WeworkLoopImpl { * 2.获取消息列表 */ fun getChatMessageList(timeout: Long = 3000): Boolean { + if (Constant.autoReply == 0) return true AccessibilityUtil.performScrollDown(getRoot(), 0) - val roomType = WeworkRoomUtil.getRoomType(getRoot()) - var titleList = WeworkRoomUtil.getRoomTitle(getRoot()) + val roomType = WeworkRoomUtil.getRoomType() + var titleList = WeworkRoomUtil.getRoomTitle() if (titleList.contains("对方正在输入…")) { titleList = WeworkRoomUtil.getFriendName() } @@ -183,7 +184,7 @@ object WeworkLoopImpl { //回到上一页 var retry = 5 while (retry-- > 0 && !isAtHome()) { - val textView = AccessibilityUtil.findOnceByText(getRoot(), "新的客户") + val textView = AccessibilityUtil.findOnceByText(getRoot(), "新的客户", "新的居民") if (textView == null) { backPress() } @@ -197,6 +198,7 @@ object WeworkLoopImpl { * 读取聊天列表 */ private fun getChatroomList(): Boolean { + if (Constant.autoReply == 0) return true if (!isAtHome()) return true if (logIndex % 3 == 0) { AccessibilityUtil.performScrollUp(getRoot(), 0) diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt b/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt index 015bd03..97f6152 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkOperationImpl.kt @@ -47,7 +47,7 @@ object WeworkOperationImpl { */ fun replyMessage( titleList: List, - receivedName: String, + receivedName: String?, originalContent: String, textType: Int, receivedContent: String @@ -70,7 +70,11 @@ object WeworkOperationImpl { } else { LogUtils.d("$title: 回复失败 直接发送答案") error("$title: 回复失败 直接发送答案 $receivedContent") - sendChatMessage(receivedContent, "[自动回复]【$originalContent】@$receivedName\n") + if (receivedName == null) { + sendChatMessage(receivedContent, "[自动回复]【$originalContent】\n") + } else { + sendChatMessage(receivedContent, "[自动回复]【$originalContent】@$receivedName\n") + } WeworkLoopImpl.getChatMessageList() } } else { @@ -432,8 +436,7 @@ object WeworkOperationImpl { sleep(Constant.POP_WINDOW_INTERVAL) val listViewList = AccessibilityUtil.findAllByClazz(getRoot(), Views.ListView) if (!listViewList.isNullOrEmpty()) { -// if (AccessibilityUtil.findTextAndClick(listViewList.last(), "添加客户")) { - if (AccessibilityUtil.findTextAndClick(listViewList.last(), "添加")) { + if (AccessibilityUtil.findTextAndClick(listViewList.last(), "添加客户", "添加居民", "加微信")) { AccessibilityUtil.findTextAndClick(getRoot(), "搜索手机号添加") AccessibilityUtil.findTextInput(getRoot(), friend.phone.trim()) if (AccessibilityUtil.findTextAndClick(getRoot(), "网络查找手机")) { @@ -500,7 +503,7 @@ object WeworkOperationImpl { } if (AccessibilityUtil.findTextAndClick(getRoot(), "添加为联系人")) { LogUtils.d("添加好友成功: " + friend.phone) - if (AccessibilityUtil.findTextAndClick(getRoot(), "发送添加邀请")) { + if (AccessibilityUtil.findTextAndClick(getRoot(), "发送添加邀请", "发送申请")) { LogUtils.d("发送添加邀请成功: " + friend.phone) } } else { @@ -565,38 +568,33 @@ object WeworkOperationImpl { private fun relaySelectTarget(selectList: List, extraText: String? = null): Boolean { val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) if (list != null) { - val frontNode = AccessibilityUtil.findFrontNode(list) + val frontNode = AccessibilityUtil.findFrontNode(list, 2) val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView) if (textViewList.size >= 2) { val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2] val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1] AccessibilityUtil.performClick(multiButton) - sleep(1000) AccessibilityUtil.performClick(searchButton) - //todo 搜索需要在循环内 - sleep(1000) for (select in selectList) { - AccessibilityUtil.findTextInput(getRoot(), select) - sleep(2000) + AccessibilityUtil.findTextInput(getRoot(), select.replace("…", "").replace("-.*$".toRegex(), "")) + sleep(Constant.CHANGE_PAGE_INTERVAL * 2) val selectListView = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) - val imageView = - AccessibilityUtil.findOnceByClazz(selectListView, Views.ImageView) + val imageView = AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView) if (imageView != null) { AccessibilityUtil.performClick(imageView) } - sleep(1000) + sleep(Constant.CHANGE_PAGE_INTERVAL) } val confirmButton = AccessibilityUtil.findOneByText(getRoot(), "确定(${selectList.size})") if (confirmButton != null) { AccessibilityUtil.performClick(confirmButton) - sleep(1000) + sleep(Constant.POP_WINDOW_INTERVAL) if (!extraText.isNullOrBlank()) { LogUtils.d("extraText: $extraText") AccessibilityUtil.findTextInput(getRoot(), extraText) - sleep(1000) } - val sendButtonList = AccessibilityUtil.findAllByText(getRoot(), "发送", timeout = 0) + val sendButtonList = AccessibilityUtil.findAllByText(getRoot(), "发送") for (sendButton in sendButtonList.filter { it.text != null }) { if (sendButton.text == "发送" || sendButton.text == "发送(${selectList.size})") { AccessibilityUtil.performClick(sendButton) @@ -623,10 +621,10 @@ object WeworkOperationImpl { */ private fun createGroup(): Boolean { goHomeTab("工作台") - val textViewGroup = AccessibilityUtil.scrollAndFindByText(getRoot(), "客户群") + val textViewGroup = AccessibilityUtil.scrollAndFindByText(getRoot(), "客户群", "居民群") if (AccessibilityUtil.performClick(textViewGroup)) { LogUtils.d("进入客户群应用") - val textView = AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群") + val textView = AccessibilityUtil.findOneByText(getRoot(), "创建一个客户群", "创建一个居民群") AccessibilityUtil.performClick(textView) return true } else { @@ -681,15 +679,18 @@ object WeworkOperationImpl { } val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) if (list != null) { - val frontNode = AccessibilityUtil.findFrontNode(list) + val frontNode = AccessibilityUtil.findFrontNode(list, 2) val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView) if (textViewList.size >= 2) { val multiButton = textViewList.lastOrNull() for (select in selectList) { AccessibilityUtil.performClick(multiButton) AccessibilityUtil.findTextInput(getRoot(), select) - val selectListView = + sleep(Constant.POP_WINDOW_INTERVAL) + val selectListViewTemp = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) + val selectListView = + AccessibilityUtil.findOnceByClazz(getRoot(), Views.RecyclerView) ?: selectListViewTemp val imageView = AccessibilityUtil.findOneByClazz(selectListView, Views.ImageView, root = false) AccessibilityUtil.performClick(imageView) @@ -740,7 +741,7 @@ object WeworkOperationImpl { } val list = AccessibilityUtil.findOneByClazz(getRoot(), Views.ListView) if (list != null) { - val frontNode = AccessibilityUtil.findFrontNode(list) + val frontNode = AccessibilityUtil.findFrontNode(list, 2) val textViewList = AccessibilityUtil.findAllOnceByClazz(frontNode, Views.TextView) if (textViewList.size >= 2) { val multiButton = textViewList.lastOrNull() diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkService.kt b/app/src/main/java/org/yameida/worktool/service/WeworkService.kt index bf3d3d3..ae56546 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkService.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkService.kt @@ -25,6 +25,7 @@ import java.lang.Exception * event.source则不需要验证包名获取窗口并可获得事件详情 */ class WeworkService : AccessibilityService() { + private val TAG = "WeworkService" lateinit var webSocketManager: WebSocketManager override fun onServiceConnected() { @@ -85,10 +86,15 @@ class WeworkService : AccessibilityService() { } class EchoWebSocketListener() : WebSocketListener() { + private val TAG = "WeworkService.EchoWebSocketListener" private lateinit var socket: WebSocket override fun onOpen(webSocket: WebSocket, response: Response) { socket = webSocket - Log.e("ChatMaskActivityListener", "链接建立") + Log.e(TAG, "链接建立") + val robotId = SPUtils.getInstance().getString(WebConfig.LISTEN_CHANNEL_ID, "") + val appVersion = SPUtils.getInstance().getString("appVersion", "") + val workVersion= SPUtils.getInstance().getString("workVersion", "") + log("链接建立: $robotId appVersion: $appVersion workVersion: $workVersion") } override fun onMessage(webSocket: WebSocket, text: String) { @@ -104,19 +110,19 @@ class WeworkService : AccessibilityService() { override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { super.onClosed(webSocket, code, reason) //服务器关闭后 - Log.e("ChatMaskActivityListener", "链接关闭 $reason") + Log.e(TAG, "链接关闭 $reason") } override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { super.onClosing(webSocket, code, reason) socket.close(code, reason) - Log.e("ChatMaskActivityListener", "服务端关闭连接 $code: $reason") + Log.e(TAG, "服务端关闭连接 $code: $reason") } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { //服务器中断 - Log.e("ChatMaskActivityListener", "链接错误: " + t.toString() + response.toString()) + Log.e(TAG, "链接错误: " + t.toString() + response.toString()) } } } \ No newline at end of file diff --git a/app/src/main/java/org/yameida/worktool/utils/AccessibilityUtil.kt b/app/src/main/java/org/yameida/worktool/utils/AccessibilityUtil.kt index c225739..7683082 100644 --- a/app/src/main/java/org/yameida/worktool/utils/AccessibilityUtil.kt +++ b/app/src/main/java/org/yameida/worktool/utils/AccessibilityUtil.kt @@ -70,8 +70,8 @@ object AccessibilityUtil { } //寻找第一个文本匹配(关键词)并点击 - fun findTextAndClick(nodeInfo: AccessibilityNodeInfo?, text: String): Boolean { - val textView = findOneByText(nodeInfo, text) ?: return false + fun findTextAndClick(nodeInfo: AccessibilityNodeInfo?, vararg textList: String): Boolean { + val textView = findOneByText(nodeInfo, *textList) ?: return false return performClick(textView) } @@ -113,20 +113,20 @@ object AccessibilityUtil { //滚动并按文本寻找第一个控件 fun scrollAndFindByText( nodeInfo: AccessibilityNodeInfo, - text: String, + vararg textList: String, maxRetry: Int = 3 ): AccessibilityNodeInfo? { var index = 0 while (index++ < maxRetry) { performScrollUp(nodeInfo, 0) - val node = findOnceByText(nodeInfo, text) + val node = findOnceByText(nodeInfo, *textList) if (node != null) { return node } } while (index++ < maxRetry * 2) { performScrollDown(nodeInfo, 0) - val node = findOnceByText(nodeInfo, text) + val node = findOnceByText(nodeInfo, *textList) if (node != null) { return node } @@ -369,12 +369,12 @@ object AccessibilityUtil { /** * 按文本(关键词)寻找节点和子节点内的一个匹配项 * @param node 节点 - * @param text 关键词 + * @param textList 关键词 * @param timeout 检查超时时间 */ fun findOneByText( node: AccessibilityNodeInfo?, - text: String, + vararg textList: String, exact: Boolean = false, timeout: Long = 5000, root: Boolean = true @@ -383,18 +383,9 @@ object AccessibilityUtil { val startTime = System.currentTimeMillis() var currentTime = startTime while (currentTime - startTime <= timeout) { - val textViewList = node.findAccessibilityNodeInfosByText(text) - LogUtils.v("text: $text count: " + textViewList.size) - if (textViewList != null && textViewList.size > 0) { - for (textView in textViewList) { - if (textView.text == text) { - return textView - } - } - if (!exact) { - return textViewList[0] - } - } + val result = findOnceByText(node, *textList, exact = exact) + LogUtils.v("text: $textList result == null: ${result == null}") + if (result != null) return result sleep(SHORT_INTERVAL) if (root) { node = getRoot(true) @@ -403,43 +394,53 @@ object AccessibilityUtil { } currentTime = System.currentTimeMillis() } - Log.e(tag, "findOneByText: not found: $text") + Log.e(tag, "findOneByText: not found: $textList") return null } fun findOnceByText( node: AccessibilityNodeInfo?, - text: String, + vararg textList: String, exact: Boolean = false ): AccessibilityNodeInfo? { - return findOneByText(node, text, exact, 0) + if (node == null) return null + val textNodeList = findAllOnceByText(node, *textList, exact = exact) + LogUtils.v("text: $textList count: " + textNodeList.size) + if (exact) return textNodeList[0] + else if (textNodeList.size > 0) { + for (textNode in textNodeList) { + for (text in textList) { + if (textNode.text == text) { + return textNode + } + } + } + return textNodeList[0] + } + return null } /** * 按文本(关键词)寻找节点和子节点内的所有匹配项 * @param node 节点 - * @param text 关键词 + * @param textList 关键词 * @param timeout 检查超时时间 */ fun findAllByText( node: AccessibilityNodeInfo?, - text: String, + vararg textList: String, exact: Boolean = false, timeout: Long = 5000, - root: Boolean = true + root: Boolean = true, + minSize: Int = 1 ): List { var node = node ?: return arrayListOf() val startTime = System.currentTimeMillis() var currentTime = startTime while (currentTime - startTime <= timeout) { - val tvList = node.findAccessibilityNodeInfosByText(text) - if (tvList != null && tvList.size > 0) { - if (!exact) { - return tvList - } else if (tvList.count { it.text == text } > 0) { - return tvList.filter { it.text == text } - } - } + val result = findAllOnceByText(node, *textList, exact = exact) + LogUtils.v("text: $textList count: " + result.size) + if (result.size >= minSize) return result sleep(SHORT_INTERVAL) if (root) { node = getRoot(true) @@ -448,10 +449,39 @@ object AccessibilityUtil { } currentTime = System.currentTimeMillis() } - Log.e(tag, "findAllByText: not found: $text") + Log.e(tag, "findAllByText: not found: $textList") return arrayListOf() } + /** + * 按文本(关键词)寻找节点和子节点内的所有匹配项 + * node 节点 + * clazz 类名 + * limitDepth 深度 限制深度搜索深度必须匹配提供值且类名相同才返回 不填默认不限制 + */ + fun findAllOnceByText( + node: AccessibilityNodeInfo?, + vararg textList: String, + exact: Boolean = false, + list: ArrayList = ArrayList() + ): ArrayList { + if (node == null) return list + val nodeText = node.text + if (nodeText != null) { + for (text in textList) { + if (exact && nodeText == text) { + list.add(node) + } else if (!exact && nodeText.contains(text)) { + list.add(node) + } + } + } + for (i in 0 until node.childCount) { + findAllOnceByText(node.getChild(i), *textList, exact = exact, list = list) + } + return list + } + /** * 按类名寻找节点和子节点内的一个匹配项 * node 节点 @@ -468,10 +498,6 @@ object AccessibilityUtil { root: Boolean = true ): AccessibilityNodeInfo? { var node = node ?: return null - if (node.className == clazz) { - if (limitDepth == null || limitDepth == depth) - return node - } val startTime = System.currentTimeMillis() var currentTime = startTime while (currentTime - startTime <= timeout) { @@ -524,12 +550,11 @@ object AccessibilityUtil { fun findAllByClazz( node: AccessibilityNodeInfo?, clazz: String, - list: ArrayList = ArrayList(), timeout: Long = 5000, root: Boolean = true, minSize: Int = 1 ): ArrayList { - var node = node ?: return list + var node = node ?: return arrayListOf() val startTime = System.currentTimeMillis() var currentTime = startTime while (currentTime - startTime <= timeout) { @@ -546,7 +571,7 @@ object AccessibilityUtil { } LogUtils.e("findAllByClazz Exception()") Exception().printStackTrace() - return list + return arrayListOf() } /** @@ -568,11 +593,23 @@ object AccessibilityUtil { return list } + /** + * 查找节点的前兄弟节点 直到该节点满足子节点数 + * node 节点 + */ + fun findFrontNode(node: AccessibilityNodeInfo?, minChildCount: Int = 0): AccessibilityNodeInfo? { + var findFrontNode = findFrontNode(node) ?: return null + while (findFrontNode.childCount < minChildCount) { + findFrontNode = findFrontNode(findFrontNode) ?: return null + } + return findFrontNode + } + /** * 查找节点的前兄弟节点 * node 节点 */ - fun findFrontNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? { + private fun findFrontNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? { if (node == null) return null var parent: AccessibilityNodeInfo? = node.parent var son: AccessibilityNodeInfo? = node @@ -593,11 +630,23 @@ object AccessibilityUtil { return null } + /** + * 查找节点的后兄弟节点 直到该节点满足子节点数 + * node 节点 + */ + fun findBackNode(node: AccessibilityNodeInfo?, minChildCount: Int = 0): AccessibilityNodeInfo? { + var findBackNode = findBackNode(node) ?: return null + while (findBackNode.childCount < minChildCount) { + findBackNode = findFrontNode(findBackNode) ?: return null + } + return findBackNode + } + /** * 查找节点的后兄弟节点 * node 节点 */ - fun findBackNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? { + private fun findBackNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? { if (node == null) return null var parent: AccessibilityNodeInfo? = node.parent var son: AccessibilityNodeInfo? = node diff --git a/app/src/main/java/org/yameida/worktool/utils/UpdateUtil.kt b/app/src/main/java/org/yameida/worktool/utils/UpdateUtil.kt index 8a22441..39512a9 100644 --- a/app/src/main/java/org/yameida/worktool/utils/UpdateUtil.kt +++ b/app/src/main/java/org/yameida/worktool/utils/UpdateUtil.kt @@ -16,12 +16,6 @@ import update.UpdateAppUtils object UpdateUtil { fun checkUpdate() { -// val remoteVersionCode = 10 -// val remoteVersionName = "1.0.1" -// val forceUpdate = false -// val updateLog = "修复Bug\n优化用户体验" -// val downloadUrl = "https://down.qq.com/qqweb/QQ_1/android_apk/Android_8.5.0.5025_537066738.apk" -// val fileMD5 = "560017dc94e8f9b65f4ca997c7feb326" OkGo.get(Constant.URL_CHECK_UPDATE) .execute(object : StringCallback() { override fun onSuccess(response: Response) { diff --git a/app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt b/app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt index 67fdf4c..f9cfc9f 100644 --- a/app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt +++ b/app/src/main/java/org/yameida/worktool/utils/WeworkRoomUtil.kt @@ -21,22 +21,22 @@ object WeworkRoomUtil { * 房间类型 ROOM_TYPE * @see WeworkMessageBean.ROOM_TYPE */ - fun getRoomType(root: AccessibilityNodeInfo, print: Boolean = true): Int { + fun getRoomType(print: Boolean = true): Int { + val roomTitle = getRoomTitle() when { - isExternalSingleChat(root) -> { + isExternalSingleChat(roomTitle) -> { LogUtils.d("ROOM_TYPE: ROOM_TYPE_EXTERNAL_CONTACT") return WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT } - isGroupChat(root) -> { - return if (isExternalGroup(root)) { - LogUtils.d("ROOM_TYPE: ROOM_TYPE_EXTERNAL_GROUP") - WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP - } else { - LogUtils.d("ROOM_TYPE: ROOM_TYPE_INTERNAL_GROUP") - WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP - } + isExternalGroup() -> { + LogUtils.d("ROOM_TYPE: ROOM_TYPE_EXTERNAL_GROUP") + return WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP } - isSingleChat(root) -> { + isGroupChat(roomTitle) -> { + LogUtils.d("ROOM_TYPE: ROOM_TYPE_INTERNAL_GROUP") + return WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP + } + isSingleChat() -> { LogUtils.d("ROOM_TYPE: ROOM_TYPE_INTERNAL_CONTACT") return WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT } @@ -55,19 +55,23 @@ object WeworkRoomUtil { * @see WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP * @see WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT */ - fun getRoomTitle(root: AccessibilityNodeInfo): ArrayList { + fun getRoomTitle(print: Boolean = true): ArrayList { val titleList = arrayListOf() - val list = AccessibilityUtil.findOnceByClazz(root, Views.ListView) + val list = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ListView) if (list != null) { val frontNode = findFrontNode(list.parent.parent) val textViewList = findAllOnceByClazz(frontNode, Views.TextView) for (textView in textViewList) { if (!textView.text.isNullOrBlank()) { - titleList.add(textView.text.toString().replace("\\(\\d+\\)$".toRegex(), "")) + val text = textView.text.toString() + titleList.add(text.replace("\\(\\d+\\)$".toRegex(), "")) + if (text.contains("\\(\\d+\\)$".toRegex())) { + titleList.add(text) + } } } } - LogUtils.v("getRoomTitle: ", titleList) + if (print) LogUtils.v("getRoomTitle: ", titleList) return titleList } @@ -76,8 +80,8 @@ object WeworkRoomUtil { */ fun intoRoom(title: String): Boolean { LogUtils.d("intoRoom(): $title") - val titleList = getRoomTitle(getRoot()) - val roomType = getRoomType(getRoot()) + val titleList = getRoomTitle(false) + val roomType = getRoomType() if (roomType != WeworkMessageBean.ROOM_TYPE_UNKNOWN && titleList.count { it.replace("…", "").replace("\\(.*?\\)".toRegex(), "") == title.replace("…", "") @@ -96,7 +100,7 @@ object WeworkRoomUtil { val searchButton: AccessibilityNodeInfo = textViewList[textViewList.size - 2] val multiButton: AccessibilityNodeInfo = textViewList[textViewList.size - 1] AccessibilityUtil.performClick(searchButton) - AccessibilityUtil.findTextInput(getRoot(), title.replace("…", "")) + AccessibilityUtil.findTextInput(getRoot(), title.replace("…", "").replace("-.*$".toRegex(), "")) sleep(Constant.CHANGE_PAGE_INTERVAL) val selectListView = findOneByClazz(getRoot(), Views.ListView) val imageView = AccessibilityUtil.findOnceByClazz(selectListView, Views.ImageView) @@ -195,35 +199,22 @@ object WeworkRoomUtil { /** * 是否是群聊 - * 群右上角有两个按钮(快速会议按钮、更多按钮) + * 群名最后有(\d)显示群人数 */ - private fun isGroupChat(root: AccessibilityNodeInfo): Boolean { - val list = AccessibilityUtil.findOnceByClazz(root, Views.ListView) - if (list != null) { - val frontNode = findFrontNode(list.parent.parent) - val textViewList = findAllOnceByClazz(frontNode, Views.TextView) - if (textViewList.size >= 2) { - val buttonList = findAllOnceByClazz(textViewList.last().parent.parent, Views.TextView) - return buttonList.size == 2 - } else { - LogUtils.v("未找到群管理按钮") - } - } else { - LogUtils.d("未找到消息列表") - } - return false + private fun isGroupChat(roomTitle: ArrayList): Boolean { + return roomTitle.size > 1 && roomTitle[1].contains("\\(\\d+\\)$".toRegex()) } /** * 是否是外部群 * listview前兄弟控件 && text包含外部群 */ - private fun isExternalGroup(root: AccessibilityNodeInfo): Boolean { - val listView = AccessibilityUtil.findOnceByClazz(root, Views.ListView, null, 0) + private fun isExternalGroup(): Boolean { + val listView = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ListView, null, 0) if (listView != null) { val frontNode = findFrontNode(listView) if (frontNode != null) { - val nodeList = AccessibilityUtil.findAllByText(frontNode, "外部群", timeout = 0) + val nodeList = AccessibilityUtil.findAllOnceByText(frontNode, "外部群") return nodeList.isNotEmpty() } } @@ -234,9 +225,9 @@ object WeworkRoomUtil { * 是否是单聊 * 有列表和输入框 */ - private fun isSingleChat(root: AccessibilityNodeInfo): Boolean { - val list = AccessibilityUtil.findOnceByClazz(root, Views.ListView) - val editText = AccessibilityUtil.findOnceByClazz(root, Views.EditText) + private fun isSingleChat(): Boolean { + val list = AccessibilityUtil.findOnceByClazz(getRoot(), Views.ListView) + val editText = AccessibilityUtil.findOnceByClazz(getRoot(), Views.EditText) if (list != null && editText != null) { return true } @@ -247,8 +238,7 @@ object WeworkRoomUtil { * 是否是外部单聊 * 姓名下面有@xx */ - private fun isExternalSingleChat(root: AccessibilityNodeInfo): Boolean { - val roomTitle = getRoomTitle(root) + private fun isExternalSingleChat(roomTitle: ArrayList): Boolean { return roomTitle.size > 1 && roomTitle.count { it.matches("^[@@].*?".toRegex()) } > 0 } diff --git a/app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt b/app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt index 6bc6374..8a8cc70 100644 --- a/app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt +++ b/app/src/main/java/org/yameida/worktool/utils/WeworkTextUtil.kt @@ -282,7 +282,7 @@ object WeworkTextUtil { fun longClickMessageItem( node: AccessibilityNodeInfo?, replyTextType: Int, - replyNick: String, + replyNick: String?, replyContent: String, key: String ): Boolean { @@ -290,14 +290,24 @@ object WeworkTextUtil { for (i in 0 until node.childCount) { val item = node.getChild(node.childCount - 1 - i) ?: continue val nameList = getNameList(item) + if (nameList.isEmpty()) { + val backNode = getMessageListNode(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT) + if (backNode != null) { + val textNode = AccessibilityUtil.findOnceByText(backNode, replyContent) + if (textNode != null) { + LogUtils.d("nameList: $nameList\nreplyContent: $replyContent") + return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, key) + } + } + } for (name in nameList) { if (name == replyNick) { - val backNode = getMessageListNode(item) + val backNode = getMessageListNode(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP) if (backNode != null) { val textNode = AccessibilityUtil.findOnceByText(backNode, replyContent) if (textNode != null) { LogUtils.d("nameList: $nameList\nreplyContent: $replyContent") - return longClickMessageItem(item, key) + return longClickMessageItem(item, WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, key) } } } @@ -306,8 +316,8 @@ object WeworkTextUtil { return false } - private fun longClickMessageItem(item: AccessibilityNodeInfo, key: String): Boolean { - val backNode = getMessageListNode(item) + private fun longClickMessageItem(item: AccessibilityNodeInfo, roomType: Int, key: String): Boolean { + val backNode = getMessageListNode(item, roomType) AccessibilityUtil.performLongClickWithSon(backNode) val optionRvList = findAllByClazz(getRoot(), Views.RecyclerView) for (optionRv in optionRvList) { @@ -327,10 +337,17 @@ object WeworkTextUtil { * 适用于左侧发言者 * @param item 消息item节点 */ - private fun getMessageListNode(item: AccessibilityNodeInfo): AccessibilityNodeInfo? { - val node = AccessibilityUtil.findOnceByClazz(item, Views.ViewGroup) - if (node != null) { - return AccessibilityUtil.findBackNode(node) + private fun getMessageListNode(item: AccessibilityNodeInfo, roomType: Int): AccessibilityNodeInfo? { + if (roomType in arrayOf(WeworkMessageBean.ROOM_TYPE_INTERNAL_CONTACT, WeworkMessageBean.ROOM_TYPE_EXTERNAL_CONTACT)) { + val node = AccessibilityUtil.findOnceByClazz(item, Views.ImageView) + if (node != null) { + return AccessibilityUtil.findBackNode(node) + } + } else if (roomType in arrayOf(WeworkMessageBean.ROOM_TYPE_INTERNAL_GROUP, WeworkMessageBean.ROOM_TYPE_EXTERNAL_GROUP)) { + val node = AccessibilityUtil.findOnceByClazz(item, Views.ViewGroup) + if (node != null) { + return AccessibilityUtil.findBackNode(node) + } } return null } diff --git a/app/src/main/res/layout/activity_listen.xml b/app/src/main/res/layout/activity_listen.xml index 63ce57b..deebc05 100644 --- a/app/src/main/res/layout/activity_listen.xml +++ b/app/src/main/res/layout/activity_listen.xml @@ -150,6 +150,40 @@ + + + + + + + + + + + + + + + + + + + + + + +