填充表单

This commit is contained in:
manchuwork
2025-10-14 07:17:01 +08:00
parent 9e843c4a65
commit b2d672c7ab
18 changed files with 1713 additions and 56 deletions

View File

@@ -2,8 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@@ -11,11 +13,16 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:hardwareAccelerated="true"
android:theme="@style/Theme.Bested" >
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:excludeFromRecents="true"
android:noHistory="true"
android:taskAffinity=""
android:theme="@style/Theme.Bested" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -1,76 +1,84 @@
package com.loveerror.bested
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.content.Intent
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.loveerror.bested.handler.ZjMccmCrm
import com.loveerror.bested.tool.AccessibilityTool
import com.loveerror.bested.tool.WebViewTool
class UIInspectorService : AccessibilityService() {
private lateinit var zjMccmCrm: ZjMccmCrm
override fun onServiceConnected() {
super.onServiceConnected()
// 初始化 ZjMccmCrm
zjMccmCrm = ZjMccmCrm(this)
zjMccmCrm.initialize(this)
// 设置配置
serviceInfo = createServiceInfo()
}
override fun onUnbind(intent: Intent?): Boolean {
// 清理 ZjMccmCrm
zjMccmCrm.cleanup(this)
return super.onUnbind(intent)
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
when (event?.eventType) {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
AccessibilityEvent.TYPE_VIEW_FOCUSED -> {
// 获取根节点
val root = rootInActiveWindow
if (root != null) {
// 遍历所有节点
traverseAccessibilityNodes(root)
}
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
or AccessibilityEvent.TYPE_VIEW_CLICKED
or AccessibilityEvent.TYPE_VIEW_FOCUSED
or AccessibilityEvent.TYPE_WINDOWS_CHANGED
or AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
-> {
handleWindowChangeOpt(event)
}
}
}
private fun traverseAccessibilityNodes(node: AccessibilityNodeInfo) {
// 处理当前节点信息
processNodeInfo(node)
private fun handleWindowChangeOpt(event: AccessibilityEvent) {
// 窗口变化时检查
WebViewTool.enhanceWebViewAccessibility(rootInActiveWindow)
WebViewTool.traverseAndEnhanceNodes(rootInActiveWindow)
// 遍历子节点
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
traverseAccessibilityNodes(child)
}
}
// 打印界面树
AccessibilityTool.printViewTree(rootInActiveWindow)
}
private fun processNodeInfo(node: AccessibilityNodeInfo) {
// 提取节点信息
val nodeInfo = AccessibilityNodeInfoData(
packageName = node.packageName?.toString() ?: "",
className = node.className?.toString() ?: "",
text = node.text?.toString() ?: "",
contentDescription = node.contentDescription?.toString() ?: "",
isEnabled = node.isEnabled,
isVisible = node.isVisibleToUser,
activityName = getActiveActivityName(), // 获取当前Activity名称
viewIdResourceName = node.viewIdResourceName ?: "" // View的资源名称
)
// 可以在这里处理节点信息,比如打印日志或存储
println("Found view: $nodeInfo")
}
private fun getActiveActivityName(): String {
// 通过根节点获取当前Activity名称
val root = rootInActiveWindow
return root?.packageName?.toString() ?: ""
}
override fun onInterrupt() {
// 服务中断时的处理
}
private fun createServiceInfo(): AccessibilityServiceInfo {
val serviceInfo = AccessibilityServiceInfo()
// 事件类型
serviceInfo.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED or
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED or
AccessibilityEvent.TYPE_VIEW_FOCUSED or
AccessibilityEvent.TYPE_VIEW_CLICKED
// 反馈类型
serviceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_ALL_MASK
// 标志
serviceInfo.flags = AccessibilityServiceInfo.DEFAULT or
AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS or
AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY or
AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS or
AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
serviceInfo.notificationTimeout = 100
return serviceInfo
}
}
data class AccessibilityNodeInfoData(
val packageName: String,
val className: String,
val text: String,
val contentDescription: String,
val isEnabled: Boolean,
val isVisible: Boolean,
val activityName: String, // 当前Activity名称
val viewIdResourceName: String // View的完整资源名称
)

View File

@@ -1,15 +1,14 @@
package com.loveerror.bested
import android.accessibilityservice.AccessibilityServiceInfo
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.provider.Settings
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import android.widget.Button
@@ -18,10 +17,13 @@ import androidx.core.view.accessibility.AccessibilityManagerCompat
class MainActivity : AppCompatActivity() {
private lateinit var accessibilityStateReceiver: BroadcastReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val btnEnableService = findViewById<Button>(R.id.btnEnableService)
btnEnableService.setOnClickListener {
// 跳转到无障碍服务设置页面
@@ -29,11 +31,93 @@ class MainActivity : AppCompatActivity() {
startActivity(intent)
}
updateAccessibilityServiceButton()
// 添加显示悬浮按钮的按钮
val btnShowFloatingButton = findViewById<Button>(R.id.btnShowFloatingButton)
btnShowFloatingButton.setOnClickListener {
showFloatingButton()
}
// 初始化悬浮权限显示
updateFloatingButtonPermission()
// 应用启动后立即移至后台
// moveTaskToBack(true)
}
/**
* 更新悬浮按钮状态显示
*/
private fun updateFloatingButtonPermission() {
val tvFloatingButtonStatus = findViewById<TextView>(R.id.tvFloatingButtonStatus)
// 更新状态文本
tvFloatingButtonStatus.text = if (checkOverlayPermission()) {
"允许在其他应用上层显示权限: 允许在其他应用上层显示"
} else {
"允许在其他应用上层显示权限: 不允许在其他应用上层显示"
}
// 可以根据状态设置不同的文本颜色
tvFloatingButtonStatus.setTextColor(
if (checkOverlayPermission()) {
android.graphics.Color.GREEN
} else {
android.graphics.Color.RED
}
)
}
private fun showFloatingButton() {
if (checkOverlayPermission()) {
MyApplication.getInstance().getButtonManager().showFloatingButton()
} else {
// 如果没有权限,先请求权限
requestOverlayPermission()
}
updateFloatingButtonPermission()
}
private fun checkOverlayPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Settings.canDrawOverlays(this)
} else {
true
}
}
private fun requestOverlayPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
android.net.Uri.parse("package:$packageName")
)
startActivityForResult(intent, 1001)
}
}
// 在 onActivityResult 中确保正确处理权限获取后的显示
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 1001) {
if (checkOverlayPermission()) {
MyApplication.getInstance().getButtonManager().showFloatingButton()
}
}
}
override fun onDestroy() {
super.onDestroy()
// buttonManager.hideFloatingButton()
}
override fun onResume() {
super.onResume()
updateAccessibilityServiceButton()
// 初始化悬浮按钮状态显示
updateFloatingButtonPermission()
}
private fun updateAccessibilityServiceButton() {
@@ -69,4 +153,13 @@ class MainActivity : AppCompatActivity() {
serviceInfo.resolveInfo.serviceInfo.name == componentName.className
}
}
@SuppressLint("GestureBackNavigation", "MissingSuperCall")
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
// 不调用 super.onBackPressed(),而是直接将应用移至后台
moveTaskToBack(true)
}
}

View File

@@ -0,0 +1,54 @@
package com.loveerror.bested
import android.app.Application
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.provider.Settings
import com.loveerror.bested.button.GlobalFloatingButtonManager
import com.loveerror.bested.task.TaskManager
class MyApplication : Application() {
private lateinit var buttonManager: GlobalFloatingButtonManager
private lateinit var broadcastReceiver: BroadcastReceiver
override fun onCreate() {
super.onCreate()
instance = this
buttonManager = GlobalFloatingButtonManager(this)
buttonManager.initialize() // 初始化包括广播注册
// 应用启动时显示悬浮按钮
if (checkOverlayPermission()) {
buttonManager.showFloatingButton()
}
}
override fun onTerminate() {
super.onTerminate()
buttonManager.cleanup() // 清理包括广播注销
}
companion object {
@JvmStatic
private var instance: MyApplication? = null
@JvmStatic
fun getInstance(): MyApplication {
return instance ?: throw IllegalStateException("Application not created yet")
}
}
fun getButtonManager(): GlobalFloatingButtonManager = buttonManager
private fun checkOverlayPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Settings.canDrawOverlays(this)
} else {
true
}
}
}

View File

@@ -0,0 +1,94 @@
package com.loveerror.bested
import android.view.accessibility.AccessibilityNodeInfo
import android.accessibilityservice.AccessibilityService
import android.view.accessibility.AccessibilityEvent
class SelectAccessibility(private val service: UIInspectorService) {
fun handleSelectPopup(event: AccessibilityEvent) {
// 检查是否有新的弹窗出现
val className = event.className?.toString() ?: ""
if (className.contains("Spinner") || className.contains("AlertDialog")) {
this.handleSelectPopup()
}
}
fun handleSelectPopup() {
// 延迟一小段时间确保弹窗完全加载
Thread.sleep(500)
val root = service.rootInActiveWindow
if (root != null) {
// 查找所有可点击的选项
traverseAndFindOptions(root)
}
}
private fun traverseAndFindOptions(node: AccessibilityNodeInfo) {
// 检查当前节点是否为选项
if (isSelectableOption(node)) {
println("发现可选择项: ${node.text}")
// 可以在这里添加自动选择逻辑
performAutoSelection(node)
}
// 递归检查子节点
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
traverseAndFindOptions(child)
}
}
}
private fun isSelectableOption(node: AccessibilityNodeInfo): Boolean {
// 判断节点是否为可选择的选项
return node.isClickable &&
node.isVisibleToUser &&
!node.text.isNullOrEmpty() &&
(node.className?.contains("TextView") == true ||
node.className?.contains("CheckedTextView") == true)
}
fun performAutoSelection(optionNode: AccessibilityNodeInfo) {
// 自动选择第一个找到的选项
optionNode.performAction(AccessibilityNodeInfo.ACTION_CLICK)
println("已自动选择选项: ${optionNode.text}")
}
fun checkForSelectOptions(root: AccessibilityNodeInfo) {
// 查找包含选项的控件
val optionNodes = root.findAccessibilityNodeInfosByText("选项文本")
if (optionNodes.isNotEmpty()) {
// 检测到选择框选项
handleSelectOptions(optionNodes)
}
}
fun selectOptionByText(optionText: String) {
val root = service.rootInActiveWindow
if (root != null) {
val optionNodes = root.findAccessibilityNodeInfosByText(optionText)
optionNodes.firstOrNull()?.let { node ->
if (node.isClickable) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
} else {
// 如果选项本身不可点击,尝试点击其父节点
node.parent?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}
}
private fun handleSelectOptions(optionNodes: List<AccessibilityNodeInfo>) {
optionNodes.forEach { node ->
println("发现选项: ${node.text}")
// 可以根据需要执行其他操作
}
}
fun findSelectByResourceId(root: AccessibilityNodeInfo, resourceId: String): AccessibilityNodeInfo? {
return root.findAccessibilityNodeInfosByViewId(resourceId).firstOrNull()
}
}

View File

@@ -0,0 +1,184 @@
package com.loveerror.bested.button
import android.content.Context
import android.graphics.*
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.widget.PopupMenu
class DragFloatingButton(context: Context) : View(context) {
private val paint = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
}
private var windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private var layoutParams: WindowManager.LayoutParams? = null
private var startX = 0f
private var startY = 0f
private var originalX = 0
private var originalY = 0
private var isDragging = false
init {
// 初始化时创建正确的 WindowManager.LayoutParams
layoutParams = WindowManager.LayoutParams(
150, 150,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT
)
this.layoutParams = layoutParams
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.let {
// 绘制圆形按钮
paint.color = Color.BLUE
it.drawCircle(width / 2f, height / 2f, minOf(width, height) / 2f, paint)
// 绘制菜单图标(三个点)
paint.color = Color.WHITE
paint.strokeWidth = 8f
val centerX = width / 2f
val centerY = height / 2f
val dotRadius = 4f
val spacing = 12f
it.drawCircle(centerX, centerY - spacing, dotRadius, paint)
it.drawCircle(centerX, centerY, dotRadius, paint)
it.drawCircle(centerX, centerY + spacing, dotRadius, paint)
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
startX = event.rawX
startY = event.rawY
// 确保 layoutParams 是 WindowManager.LayoutParams 类型
if (this.layoutParams is WindowManager.LayoutParams) {
layoutParams = this.layoutParams as WindowManager.LayoutParams
originalX = layoutParams?.x ?: 0
originalY = layoutParams?.y ?: 0
} else {
// 如果不是,创建一个新的 WindowManager.LayoutParams
layoutParams = WindowManager.LayoutParams(
width, height,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT
)
// 将新的 layoutParams 应用到视图
windowManager.updateViewLayout(this, layoutParams!!)
}
// if (layoutParams == null) {
// layoutParams = this.layoutParams as? WindowManager.LayoutParams
// }
originalX = layoutParams?.x ?: 0
originalY = layoutParams?.y ?: 0
isDragging = false
}
MotionEvent.ACTION_MOVE -> {
val deltaX = event.rawX - startX
val deltaY = event.rawY - startY
// 判断是否为拖拽操作(移动距离超过阈值)
if (!isDragging && (kotlin.math.abs(deltaX) > 10 || kotlin.math.abs(deltaY) > 10)) {
isDragging = true
}
if (isDragging && layoutParams != null) {
layoutParams?.x = (originalX + deltaX).toInt()
layoutParams?.y = (originalY + deltaY).toInt()
try {
windowManager.updateViewLayout(this, layoutParams!!)
} catch (e: IllegalArgumentException) {
// 处理参数类型错误
} catch (e: Exception) {
// 处理其他可能的异常
}
}
}
MotionEvent.ACTION_UP -> {
if (!isDragging) {
// 点击事件 - 显示菜单
showPopupMenu()
}
}
}
return true
}
private fun showPopupMenu() {
val popup = PopupMenu(context, this)
popup.menu.add("启动按钮")
popup.menu.add("填充表单")
popup.menu.add("设置数据源")
popup.menu.add("隐藏悬浮按钮")
popup.setOnMenuItemClickListener { item ->
when (item.title) {
"启动按钮" -> {
performStartAction()
true
}
"填充表单" -> {
performFillFormAction()
true
}
"设置数据源" -> {
performSetDataSource()
true
}
"隐藏悬浮按钮" -> {
hideFloatingButton()
true
}
else -> false
}
}
popup.show()
}
private fun performStartAction() {
// 执行启动按钮操作
context.sendBroadcast(android.content.Intent("com.loveerror.bested.START_ACTION").apply {
setPackage(context.packageName)
})
}
private fun performSetDataSource() {
// 执行设置数据源操作
context.sendBroadcast(android.content.Intent("com.loveerror.bested.SET_DATA_SOURCE").apply {
setPackage(context.packageName)
})
}
private fun hideFloatingButton() {
context.sendBroadcast(android.content.Intent("com.loveerror.bested.HIDE_FLOATING_BUTTON").apply {
setPackage(context.packageName)
})
// 或者直接通过 WindowManager 移除视图
try {
windowManager.removeView(this)
} catch (e: Exception) {
// 处理可能的异常
}
}
}
private fun DragFloatingButton.performFillFormAction() {
// 执行启动按钮操作
context.sendBroadcast(android.content.Intent("com.loveerror.bested.FILL_ACTION").apply {
setPackage(context.packageName)
})
}

View File

@@ -0,0 +1,101 @@
package com.loveerror.bested.button
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.PixelFormat
import android.os.Build
import android.view.Gravity
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
import androidx.core.content.ContextCompat
class GlobalFloatingButtonManager(private val context: Context) {
private var windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private var floatingButton: DragFloatingButton? = null
private var broadcastReceiver: BroadcastReceiver? = null
private var isEnabled = true // 添加启用状态标志
fun initialize() {
// 注册广播接收器
broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
"com.loveerror.bested.HIDE_FLOATING_BUTTON" -> {
hideFloatingButton()
}
}
}
}
val filter = IntentFilter("com.loveerror.bested.HIDE_FLOATING_BUTTON")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(broadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
ContextCompat.registerReceiver(
context,
broadcastReceiver,
filter,
ContextCompat.RECEIVER_NOT_EXPORTED
)
}
}
fun cleanup() {
broadcastReceiver?.let {
try {
context.unregisterReceiver(it)
} catch (e: Exception) {
// 处理可能的异常
}
}
}
fun showFloatingButton() {
if (floatingButton != null || !isEnabled) return
floatingButton = DragFloatingButton(context)
val params = WindowManager.LayoutParams(
150, // 按钮宽度
150, // 按钮高度
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
LayoutParams.TYPE_APPLICATION_OVERLAY
else
LayoutParams.TYPE_PHONE,
LayoutParams.FLAG_NOT_FOCUSABLE or LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT
)
params.gravity = Gravity.TOP or Gravity.START
params.x = 100
params.y = 100
windowManager.addView(floatingButton, params)
}
fun hideFloatingButton() {
floatingButton?.let {
try {
windowManager.removeView(it)
floatingButton = null
} catch (e: Exception) {
// 处理可能的异常
}
}
}
// 添加启用/禁用功能
fun enableFloatingButton(enable: Boolean) {
isEnabled = enable
if (enable) {
showFloatingButton()
} else {
hideFloatingButton()
}
}
fun isFloatingButtonEnabled(): Boolean = isEnabled
}

View File

@@ -0,0 +1,147 @@
package com.loveerror.bested.handler
import android.view.accessibility.AccessibilityNodeInfo
import com.loveerror.bested.tool.AccessibilityTool
import com.loveerror.bested.tool.NodeToolHint
import com.loveerror.bested.tool.NodeToolText
class NormalGroupCreateForm {
companion object{
class FieldName {
val fieldName: String
val hintText: String
val defaultValue: String
val type: FieldType
constructor(fieldName: String, hintText: String, defaultValue: String, type: FieldType){
this.fieldName = fieldName
this.hintText = hintText
this.defaultValue = defaultValue
this.type = type
}
}
enum class FieldType {
TEXT,
SELECT,
CHECKBOX,
RADIO,
DATE,
TIME,
NUMBER,
CURRENCY,
EMAIL,
PHONE,
ADDRESS,
URL,
PASSWORD,
FILE,
IMAGE,
VIDEO,
AUDIO,
OTHER
}
var fields = arrayListOf<FieldName>()
init {
fields.add(FieldName("集团名称", "请输入集团名称", "集团abc", FieldType.TEXT))
fields.add(FieldName("证件名称", "证件名称", "jituan", FieldType.TEXT))
fields.add(FieldName("证件号码", "请输入证件号码", "44010119900101001X", FieldType.TEXT))
fields.add(FieldName("法人代表", "请输入法人代表", "张三", FieldType.TEXT))
fields.add(FieldName("证件地址", "请输入证件地址", "北京市海淀区", FieldType.TEXT))
fields.add(FieldName("联系电话", "请输入联系电话", "13266667777", FieldType.TEXT))
fields.add(FieldName("员工数", "请输入人数", "1", FieldType.TEXT))
}
/**
* 自动填充普通集团建档表单
* @param root AccessibilityNodeInfo根节点
* @param groupName 集团名称
*/
fun autoFillForm(root: AccessibilityNodeInfo) {
val fieldMap = mapOf<String, String>(
"集团名称" to "集团abc2",
"证件名称" to "jituan2",
"证件号码" to "44010119900101001X",
"法人代表" to "张三2",
"证件地址" to "北京市海淀区2",
"联系电话" to "13288887777",
"员工数" to "1"
)
// 查找并填写集团名称输入框
val groupName = "集团abc"
for(fieldName in fields){
fillEditTextByHintText(root,fieldMap[fieldName.fieldName] ?: fieldName.defaultValue, fieldName.fieldName, fieldName.hintText)
}
// fillCompanyNameField(root, groupName)
// 可以继续添加其他字段的填充逻辑
// 例如:选择集团状态、选择集团等级等
}
/**
* 填写集团名称字段
* 根据UI树分析查找hint为"请输入集团名称"的EditText并填入值
*/
private fun fillGroupNameField(root: AccessibilityNodeInfo, value: String) {
fillEditTextByHintText(root,value, "集团名称","请输入集团名称")
}
private fun fillCompanyNameField(root: AccessibilityNodeInfo, value: String) {
fillEditTextByHintText(root,value, "证件名称","证件名称")
}
private fun fillEditTextByHintText(root: AccessibilityNodeInfo, value: String, fieldName : String, hintText: String) {
var editTextNode = NodeToolHint.findNodeByHint(root, hintText)
fillEditText(editTextNode,value,fieldName)
}
private fun fillEditText(editText: AccessibilityNodeInfo?, value: String, fieldName : String) {
if (editText == null) {
return
}
if (editText.isEditable) {
AccessibilityTool.inputText(editText, value)
println("已填写$fieldName: $value")
} else {
println("未找到${fieldName}输入框或输入框不可编辑")
}
}
/**
* 选择集团状态
* 根据UI树分析可以通过点击相关视图来展开选项
*/
fun selectGroupStatus(root: AccessibilityNodeInfo, status: String = "在网集团") {
// 查找包含"在网集团"文本的TextView并点击
val statusView = NodeToolText.findNodeByText(root, status)
if (statusView != null && statusView.isClickable) {
AccessibilityTool.performClick(statusView)
println("已选择集团状态: $status")
} else {
println("未找到集团状态选项或选项不可点击")
}
}
/**
* 选择集团等级
* 根据UI树分析可以通过点击相关视图来展开选项
*/
fun selectGroupLevel(root: AccessibilityNodeInfo, level: String = "D") {
// 查找包含"D"文本的TextView并点击
val levelView = NodeToolText.findNodeByText(root, level)
if (levelView != null && levelView.isClickable) {
AccessibilityTool.performClick(levelView)
println("已选择集团等级: $level")
} else {
println("未找到集团等级选项或选项不可点击")
}
}
}
}

View File

@@ -0,0 +1,202 @@
package com.loveerror.bested.handler
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import androidx.core.content.ContextCompat
import com.loveerror.bested.SelectAccessibility
import com.loveerror.bested.UIInspectorService
import com.loveerror.bested.tool.AccessibilityTool
import com.loveerror.bested.tool.NodeToolText
class ZjMccmCrm(private val uiInspectorService: UIInspectorService) {
private lateinit var selectAccessibility: SelectAccessibility
private var broadcastReceiver: BroadcastReceiver? = null
fun initialize(context: Context) {
// 注册广播接收器
broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
"com.loveerror.bested.START_ACTION" -> {
// startJob()
startJobAsync()
}
"com.loveerror.bested.FILL_ACTION" -> {
fillAction()
}
}
}
}
val filter = IntentFilter("com.loveerror.bested.START_ACTION")
registerReceiver(filter, context)
val filter2 = IntentFilter("com.loveerror.bested.FILL_ACTION")
registerReceiver(filter2,context )
selectAccessibility = SelectAccessibility(uiInspectorService)
// selectAccessibility.handleSelectPopup(event)
}
fun registerReceiver(filter: IntentFilter, context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(broadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
ContextCompat.registerReceiver(
context,
broadcastReceiver,
filter,
ContextCompat.RECEIVER_NOT_EXPORTED
)
}
}
fun cleanup(context: Context) {
broadcastReceiver?.let {
try {
context.unregisterReceiver(it)
} catch (e: Exception) {
// 处理可能的异常
}
}
}
private fun fillAction() {
val root = rootInActiveWindow()
if (root == null) {
return
}
NormalGroupCreateForm.autoFillForm(root)
}
fun someOptEvent(event: AccessibilityEvent?){
// 获取根节点
val root = uiInspectorService.rootInActiveWindow
if (root != null) {
val editText = AccessibilityTool.findEditTextAfterLabel(root, "集团名称")
if (editText != null) {
val hint = editText.hintText?.toString() ?: ""
println("集团名称 EditText hint: $hint")
}
// 遍历所有节点
AccessibilityTool.traverseAccessibilityNodes(root, event)
}
}
private fun startJobAsync() {
val stateMachine = ZJMCCMCRMStateMachine(uiInspectorService)
stateMachine.navigateToNormalGroupCreatePage()
}
private fun handleWindowChange() {
// 延迟执行确保界面完全加载
Thread.sleep(300)
print("Window Changed handleWindowChange")
// 打印界面树
AccessibilityTool.printViewTree(rootInActiveWindow())
}
private var lastViewTreeHash: Int = 0
private fun rootInActiveWindow(): AccessibilityNodeInfo? {
val root = uiInspectorService.rootInActiveWindow
return root
}
private fun handleContentChange() {
val root = rootInActiveWindow()
if (root != null) {
val currentHash = calculateViewTreeHash(root)
if (currentHash != lastViewTreeHash) {
lastViewTreeHash = currentHash
Thread.sleep(200)
print("Content Changed handleContentChange")
// 打印界面树
AccessibilityTool.printViewTree(root)
}
}
}
private fun calculateViewTreeHash(node: AccessibilityNodeInfo): Int {
var hash = node.toString().hashCode()
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
hash = 31 * hash + calculateViewTreeHash(child)
}
}
return hash
}
private fun checkAppSwitch(event: AccessibilityEvent?) {
// 应用切换时强制刷新界面信息
forceScanCurrentWindow(event)
}
// 在适当的位置主动调用界面扫描
private fun forceScanCurrentWindow(event: AccessibilityEvent?) {
val root = rootInActiveWindow()
if (root != null) {
// 打印界面树
AccessibilityTool.printViewTree(root)
AccessibilityTool.traverseAccessibilityNodes(root, event)
}
}
fun isZJMCCMCRMApp(root: AccessibilityNodeInfo): Boolean {
// 通过根节点的包名判断是否为目标应用
return root.packageName?.toString() == "com.ZJMCCMCRM"
}
fun isHomePage(root: AccessibilityNodeInfo): Boolean {
// 方法1: 直接查找包含"首页"文本的所有节点
// val homeNodes = root.findAccessibilityNodeInfosByText("首页")
// return homeNodes.isNotEmpty()
// printViewTree(root)
// 方法2: 遍历所有子节点查找"首页"字样
return NodeToolText.hasNodeWithText(root, "首页")
}
// GROUP_ALL_APPS_LIST
fun isGroupAllAppsList(root: AccessibilityNodeInfo): Boolean {
// 查找所有应用列表
return NodeToolText.hasNodeWithText(root, "我的应用","最近使用","集团管理","商机管理")
}
/**
* 判断当前页面是否为集团新建选择页面
* @return true表示是集团新建页面false表示不是
*/
fun isGroupCreateSelectPage(root: AccessibilityNodeInfo): Boolean {
// 查找建档方式选项
return NodeToolText.hasNodeWithText(root, "AI建档", "普通建档", "门头照建档")
}
fun isNormalGroupCreatePage(root: AccessibilityNodeInfo): Boolean {
return NodeToolText.hasNodeWithText(root,"普通建档","集团状态","证件名称")
}
}

View File

@@ -0,0 +1,11 @@
package com.loveerror.bested.handler
enum class ZJMCCMCRMState {
HOME_PAGE,
// 全部应用列表
GROUP_ALL_APPS_LIST,
GROUP_CREATE_SELECT_PAGE,
NORMAL_GROUP_CREATE_PAGE,
UNKNOWN
}

View File

@@ -0,0 +1,147 @@
package com.loveerror.bested.handler
import android.view.accessibility.AccessibilityNodeInfo
import com.loveerror.bested.UIInspectorService
import com.loveerror.bested.tool.AccessibilityTool
class ZJMCCMCRMStateMachine(private val uiInspectorService: UIInspectorService) {
private var currentState: ZJMCCMCRMState = ZJMCCMCRMState.UNKNOWN
private var targetState: ZJMCCMCRMState = ZJMCCMCRMState.UNKNOWN
private val zjMccmCrm = ZjMccmCrm(uiInspectorService)
fun navigateToNormalGroupCreatePage() {
targetState = ZJMCCMCRMState.NORMAL_GROUP_CREATE_PAGE
executeWorkflow()
}
// 定义状态转换映射
private val stateTransitionMap = mapOf(
Pair(ZJMCCMCRMState.HOME_PAGE, ZJMCCMCRMState.GROUP_ALL_APPS_LIST) to ::handleHomePageToGroupAllAppsList,
Pair(ZJMCCMCRMState.GROUP_ALL_APPS_LIST, ZJMCCMCRMState.GROUP_CREATE_SELECT_PAGE) to ::handleAllAppsListToGroupCreateSelectPage,
Pair(ZJMCCMCRMState.GROUP_CREATE_SELECT_PAGE, ZJMCCMCRMState.NORMAL_GROUP_CREATE_PAGE) to ::handleGroupCreateSelectPageToNormalGroupCreatePage
)
private fun executeWorkflow() {
Thread {
executeWorkflowInner()
return@Thread
}.start()
}
private fun executeWorkflowInner() {
val root = getRootInActiveWindow()
if (root == null) {
return
}
val isTargetApp = zjMccmCrm.isZJMCCMCRMApp(root)
if (!isTargetApp) {
return
}
// 更新当前状态
updateState(root)
// &&
while (targetState != ZJMCCMCRMState.UNKNOWN && currentState != targetState) {
// 检查是否可以导航到目标状态
if (!canNavigateToTarget()) {
println("无法从当前状态导航到目标状态")
break
}
// 检查是否已达到目标状态
if (currentState == targetState) {
println("导航完成,当前状态: $currentState")
break
}
// 获取下一个应该转换到的状态
val nextState = getNextStateForTarget()
println("下一步将转换到: $nextState")
// 验证 nextState 是否符合预期
if (nextState == ZJMCCMCRMState.UNKNOWN) {
println("无法确定下一步状态")
break
}
// 使用状态映射执行相应动作
val transitionHandler = stateTransitionMap[Pair(currentState, nextState)]
if (transitionHandler != null) {
transitionHandler.invoke(root)
currentState = nextState
}
updateState(root)
}
}
private fun getRootInActiveWindow(): AccessibilityNodeInfo? {
return uiInspectorService.rootInActiveWindow
}
// 实现状态回退机制
// 在 ZJMCCMCRMStateMachine 类中添加
private fun canNavigateToTarget(): Boolean {
return when (targetState) {
ZJMCCMCRMState.NORMAL_GROUP_CREATE_PAGE -> {
// 从任何状态都可以导航到 NORMAL_GROUP_CREATE_PAGE
true
}
else -> false
}
}
private fun getNextStateForTarget(): ZJMCCMCRMState {
return when (targetState) {
ZJMCCMCRMState.NORMAL_GROUP_CREATE_PAGE -> {
// 确定到达目标状态的下一个必要步骤
when (currentState) {
ZJMCCMCRMState.HOME_PAGE -> ZJMCCMCRMState.GROUP_ALL_APPS_LIST
ZJMCCMCRMState.GROUP_ALL_APPS_LIST -> ZJMCCMCRMState.GROUP_CREATE_SELECT_PAGE
ZJMCCMCRMState.GROUP_CREATE_SELECT_PAGE -> ZJMCCMCRMState.NORMAL_GROUP_CREATE_PAGE
else -> ZJMCCMCRMState.UNKNOWN
}
}
else -> ZJMCCMCRMState.UNKNOWN
}
}
private fun updateState(root: AccessibilityNodeInfo) {
currentState = when {
zjMccmCrm.isHomePage(root) -> ZJMCCMCRMState.HOME_PAGE
zjMccmCrm.isGroupAllAppsList(root) -> ZJMCCMCRMState.GROUP_ALL_APPS_LIST
zjMccmCrm.isGroupCreateSelectPage(root) -> ZJMCCMCRMState.GROUP_CREATE_SELECT_PAGE
zjMccmCrm.isNormalGroupCreatePage(root) -> ZJMCCMCRMState.NORMAL_GROUP_CREATE_PAGE
else -> ZJMCCMCRMState.UNKNOWN
}
}
private fun handleHomePageToGroupAllAppsList(root: AccessibilityNodeInfo) {
AccessibilityTool.printViewTree(root)
AccessibilityTool.clickButtonByText(root, "常用功能", "全部")
println("常用功能 全部 clicked")
Thread.sleep(1000)
}
private fun handleAllAppsListToGroupCreateSelectPage(root: AccessibilityNodeInfo) {
AccessibilityTool.printViewTree(root)
val updatedRoot = uiInspectorService.rootInActiveWindow
AccessibilityTool.clickButtonByText(updatedRoot, "集团新建")
Thread.sleep(10 * 1000)
}
private fun handleGroupCreateSelectPageToNormalGroupCreatePage(root: AccessibilityNodeInfo) {
AccessibilityTool.printViewTree(root, "before普通建档")
AccessibilityTool.clickButtonByText(root, "普通建档")
Thread.sleep(15 * 1000)
AccessibilityTool.printViewTree(root, "after普通建档")
}
}

View File

@@ -0,0 +1,49 @@
package com.loveerror.bested.task
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import androidx.core.content.ContextCompat
class TaskManager (private val context: Context){
private var broadcastReceiver: BroadcastReceiver? = null
fun initialize(callbackHandler:()-> Unit) {
broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
"com.loveerror.bested.START_ACTION" -> {
// handleStartAction()
callbackHandler()
}
}
}
}
val filter = IntentFilter("com.loveerror.bested.START_ACTION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(broadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
ContextCompat.registerReceiver(
context,
broadcastReceiver,
filter,
ContextCompat.RECEIVER_NOT_EXPORTED
)
}
}
fun cleanup() {
broadcastReceiver?.let {
try {
context.unregisterReceiver(it)
} catch (e: Exception) {
// 异常处理
}
}
}
}

View File

@@ -0,0 +1,228 @@
package com.loveerror.bested.tool
import android.os.Bundle
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
class AccessibilityTool {
companion object {
fun focus(node: AccessibilityNodeInfo) {
if( node.isFocusable){
node.performAction(AccessibilityNodeInfo.ACTION_FOCUS)
}
}
fun inputText(editTextNode: AccessibilityNodeInfo, value: String): Boolean {
focus(editTextNode)
clickButton(editTextNode)
if (editTextNode.className.toString().contains("EditText")) {
val arguments = Bundle()
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, value)
if (editTextNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)) {
// 输入成功
return true
}
}
return false
}
fun performClick(btn: AccessibilityNodeInfo) {
android.os.Handler(android.os.Looper.getMainLooper()).post {
clickButton(btn)
}
}
fun clickButtonByText(root: AccessibilityNodeInfo, vararg targetTexts: String) {
// 查找可点击的"全部"按钮
val node = NodeToolText.findNodeByText(root,*targetTexts)
if (node == null) {
return
}
// 使用 Handler 切换到主线程执行点击
android.os.Handler(android.os.Looper.getMainLooper()).post {
clickButton(node)
}
}
fun clickButton(node: AccessibilityNodeInfo) {
if (node.isClickable) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
return
} else {
// 如果节点本身不可点击,尝试查找可点击的父节点
var parent = node.parent
while (parent != null) {
if (parent.isClickable) {
parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
return
}
parent = parent.parent
}
}
}
fun printViewTree(root: AccessibilityNodeInfo?, pre : String = "") {
if (root == null) {
println("=== ${pre} View Tree Start [root==null] ===")
println("=== ${pre} View Tree End [root==null] ===")
return
}
println("=== ${pre} View Tree Start ===")
printNodeTree(root, 0, pre)
println("=== ${pre} View Tree End ===")
}
private fun printNodeTree(node: AccessibilityNodeInfo, depth: Int, pre : String = "") {
val indent = " ".repeat(depth)
val nodeInfo = "${node.className?.toString() ?: "Unknown"} " +
"[text=${node.text?.toString() ?: ""}] " +
"[desc=${node.contentDescription?.toString() ?: ""}] " +
"[id=${node.viewIdResourceName ?: ""}] " +
"[hint=${node.hintText?.toString() ?: ""}] " +
"[clickable=${node.isClickable}] " +
"[longClickable=${node.isLongClickable}] " +
"[editable=${node.isEditable}] " +
"[focusable=${node.isFocusable}] " +
"[focus=${node.isFocused}] " +
"[selected=${node.isSelected}] " +
"[checkable=${node.isCheckable}] " +
"[checked=${node.isChecked}] " +
"[scrollable=${node.isScrollable}] " +
"[visibleToUser=${node.isVisibleToUser}] " +
"[password=${node.isPassword}] " +
"[multiple=${node.isMultiLine}] " +
"[accessibilityFocus=${node.isAccessibilityFocused}] " +
"[package=${node.packageName?.toString() ?: ""}] " +
"[focused=${node.isFocused}] " +
"[enabled=${node.isEnabled}] " +
"[focusable=${node.isFocusable}] " +
"[accessible=${node.isAccessibilityFocused}] " +
"[visible=${node.isVisibleToUser}] " +
""
println("${pre} $indent$nodeInfo")
// 递归打印子节点
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
printNodeTree(child, depth + 1)
}
}
}
fun findEditTextAfterLabel(root: AccessibilityNodeInfo, label: String): AccessibilityNodeInfo? {
val nodes = mutableListOf<AccessibilityNodeInfo>()
collectAllNodes(root, nodes)
var foundLabel = false
for (node in nodes) {
// 如果找到了标签
if (node.text?.toString() == label) {
foundLabel = true
continue
}
// 在找到标签后,返回下一个 EditText
if (foundLabel && node.className == "android.widget.EditText") {
return node
}
}
return null
}
private fun collectAllNodes(node: AccessibilityNodeInfo, nodes: MutableList<AccessibilityNodeInfo>) {
nodes.add(node)
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
collectAllNodes(child, nodes)
}
}
}
fun traverseAccessibilityNodes(node: AccessibilityNodeInfo?,event: AccessibilityEvent?) {
if (node == null) {
return
}
// 处理当前节点信息
processNodeInfo(node,event)
// 遍历子节点
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
traverseAccessibilityNodes(child,event)
}
}
}
private fun processNodeInfo(node: AccessibilityNodeInfo,event: AccessibilityEvent?) {
// 只处理 EditText 控件
if (node.className == "android.widget.EditText") {
val hint = node.hintText?.toString() ?: ""
println("EditText hint: $hint")
}
// 提取节点信息
val nodeInfo = AccessibilityNodeInfoData(
packageName = node.packageName?.toString() ?: "",
className = node.className?.toString() ?: "",
text = node.text?.toString() ?: "",
contentDescription = node.contentDescription?.toString() ?: "",
hint = node.hintText?.toString() ?: "", // 获取提示信息
isEnabled = node.isEnabled,
isVisible = node.isVisibleToUser,
activityName = getActiveActivityName(event), // 获取当前Activity名称
viewIdResourceName = node.viewIdResourceName ?: "" // View的资源名称
)
// 可以在这里处理节点信息,比如打印日志或存储
println("Found view: $nodeInfo")
}
private fun getActiveActivityName(event: AccessibilityEvent?): String {
// 从事件中获取当前Activity名称
val currentActivityName = event?.className?.toString() ?: ""
// print("Current Activity: $currentActivityName")
return currentActivityName
}
data class AccessibilityNodeInfoData(
val packageName: String,
val className: String,
val text: String,
val contentDescription: String,
val hint: String, // 添加提示信息字段
val isEnabled: Boolean,
val isVisible: Boolean,
val activityName: String, // 当前Activity名称
val viewIdResourceName: String // View的完整资源名称
)
}
}

View File

@@ -0,0 +1,94 @@
package com.loveerror.bested.tool
import android.view.accessibility.AccessibilityNodeInfo
class NodeToolClassName {
companion object{
fun findAllNodeByClassName(node: AccessibilityNodeInfo, className: String): List<AccessibilityNodeInfo> {
return findAllNodeByClassName(node, 0, className, ArrayList())
}
private fun findAllNodeByClassName(node: AccessibilityNodeInfo, depth: Int, className: String, retList: ArrayList<AccessibilityNodeInfo>): List<AccessibilityNodeInfo> {
if (className.isEmpty()) return emptyList()
// 检查当前节点是否匹配第一级文本
if (node.className?.toString()?.equals(className) == true) {
retList.add(node)
}
// 在子节点中查找下一级文本
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
findAllNodeByClassName(child, depth + 1, className, retList)
}
}
return retList
}
fun findFirstNodeByClassName(node: AccessibilityNodeInfo, vararg classNames: String): AccessibilityNodeInfo? {
return findFirstNodeByClassName(node, 0, classNames.toList())
}
private fun findFirstNodeByClassName(node: AccessibilityNodeInfo, depth: Int, classNames: List<String>): AccessibilityNodeInfo? {
if (classNames.isEmpty()) return null
val currentClasName = classNames[0]
val remainingClassNames = classNames.drop(1)
// 检查当前节点是否匹配第一级文本
if (node.className?.toString()?.equals(currentClasName) == true) {
// 如果没有更多层级需要查找,返回当前节点
if (remainingClassNames.isEmpty()) {
return node
}
// 如果还有更多层级,继续在子节点中查找
else {
// 在子节点中查找下一级文本
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
val result = findFirstNodeByClassName(child, depth + 1, remainingClassNames)
if (result != null) {
return result
}
}
}
}
} else {
// 如果当前节点不匹配且没有更多层级,在子节点中查找第一级文本
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
val result = findFirstNodeByClassName(child, depth + 1, classNames)
if (result != null) {
return result
}
}
}
}
return null
}
/**
* 遍历节点检查是否包含指定文本
*/
fun hasNodeWithClassName(node: AccessibilityNodeInfo, className: String): Boolean {
// 检查当前节点文本
if (node.className?.toString()?.equals(className) == true) {
return true
}
// 递归检查子节点
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null && hasNodeWithClassName(child, className)) {
return true
}
}
return false
}
}
}

View File

@@ -0,0 +1,54 @@
package com.loveerror.bested.tool
import android.view.accessibility.AccessibilityNodeInfo
class NodeToolHint {
companion object{
fun findNodeByHint(node: AccessibilityNodeInfo,vararg targetHints: String): AccessibilityNodeInfo? {
return findNodeByHint(node, 0, targetHints.toList())
}
private fun findNodeByHint(node: AccessibilityNodeInfo, depth: Int, targetHints: List<String>): AccessibilityNodeInfo? {
if (targetHints.isEmpty()) return null
val currentHint = targetHints[0]
val remainingHints = targetHints.drop(1)
// 检查当前节点是否匹配第一级hint
if (node.hintText?.toString()?.contains(currentHint) == true) {
// 如果没有更多层级需要查找,返回当前节点
if (remainingHints.isEmpty()) {
return node
}
// 如果还有更多层级,继续在子节点中查找
else {
// 在子节点中查找下一级hint
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
val result = findNodeByHint(child, depth + 1, remainingHints)
if (result != null) {
return result
}
}
}
}
}
// 如果当前节点不匹配且没有更多层级在子节点中查找第一级hint
else {
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
val result = findNodeByHint(child, depth + 1, targetHints)
if (result != null) {
return result
}
}
}
}
return null
}
}
}

View File

@@ -0,0 +1,96 @@
package com.loveerror.bested.tool
import android.view.accessibility.AccessibilityNodeInfo
class NodeToolText {
companion object{
fun findNodeByText(node: AccessibilityNodeInfo,vararg targetTexts: String): AccessibilityNodeInfo? {
return findNodeByText(node, 0, targetTexts.toList())
}
private fun findNodeByText(node: AccessibilityNodeInfo, depth: Int, targetTexts: List<String>): AccessibilityNodeInfo? {
if (targetTexts.isEmpty()) return null
val currentText = targetTexts[0]
val remainingTexts = targetTexts.drop(1)
// 检查当前节点是否匹配第一级文本
if (node.text?.toString()?.contains(currentText) == true) {
// 如果没有更多层级需要查找,返回当前节点
if (remainingTexts.isEmpty()) {
return node
}
// 如果还有更多层级,继续在子节点中查找
else {
// 在子节点中查找下一级文本
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
val result = findNodeByText(child, depth + 1, remainingTexts)
if (result != null) {
return result
}
}
}
}
}
//else if (remainingTexts.isEmpty()) {
else {
// 如果当前节点不匹配且没有更多层级,在子节点中查找第一级文本
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null) {
val result = findNodeByText(child, depth + 1, targetTexts)
if (result != null) {
return result
}
}
}
}
return null
}
/**
* 遍历节点检查是否包含指定文本
*/
fun hasNodeWithText(node: AccessibilityNodeInfo, targetText: String): Boolean {
// 检查当前节点文本
if (node.text?.toString()?.contains(targetText) == true) {
return true
}
// 递归检查子节点
for (i in 0 until node.childCount) {
val child = node.getChild(i)
if (child != null && hasNodeWithText(child, targetText)) {
return true
}
}
return false
}
fun hasNodeWithText(
node: AccessibilityNodeInfo?,
vararg requiredTexts: String
): Boolean {
if (node == null) return false
val foundTexts: MutableList<String?> = ArrayList<String?>()
// 遍历页面中的TextView元素
// 这里需要根据实际UI结构来查找包含指定文本的元素
for (text in requiredTexts) {
if (NodeToolText.hasNodeWithText(node, text)){
foundTexts.add(text)
}
}
// 如果找到了至少一个建档方式文本,则认为是集团新建页面
return foundTexts.size >= requiredTexts.size
}
}
}

View File

@@ -0,0 +1,72 @@
package com.loveerror.bested.tool
import android.os.Build
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo
class WebViewTool {
companion object {
fun enhanceWebViewAccessibility(root: AccessibilityNodeInfo?) {
if (root == null) return
// 查找所有WebView
val webViews = root.findAccessibilityNodeInfosByViewId("com.target.app:id/webview")
for (webView in webViews) {
// 强制设置WebView为重要的无障碍节点
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
webView.isImportantForAccessibility = true
}
// 为WebView添加自定义描述
val collectionInfo =
CollectionInfo.obtain(1, 1, false)
webView.setCollectionInfo(collectionInfo)
// // 回收节点避免内存泄漏
// webView.recycle()
}
}
fun traverseAndEnhanceNodes(node: AccessibilityNodeInfo?) {
if (node == null) return
// 为没有内容描述的视图添加描述
if (node.getContentDescription() == null && node.getText() == null) {
val className = node.getClassName()
if (className != null) {
// 根据类名推断角色
val roleDescription = inferRoleFromClassName(className.toString())
if (roleDescription != null) {
node.setContentDescription(roleDescription)
}
}
}
// 递归处理子节点
for (i in 0..<node.getChildCount()) {
val child = node.getChild(i)
if (child != null) {
traverseAndEnhanceNodes(child)
child.recycle()
}
}
}
private fun inferRoleFromClassName(className: String): String? {
if (className.contains("Button")) return "按钮"
if (className.contains("EditText")) return "编辑框"
if (className.contains("TextView")) return "文本"
if (className.contains("Image")) return "图片"
if (className.contains("WebView")) return "网页内容"
return null
}
}
}

View File

@@ -27,4 +27,20 @@
android:text="服务状态: 未启用"
android:textSize="16sp" />
<!-- 在现有布局中添加控制按钮 -->
<Button
android:id="@+id/btnShowFloatingButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="显示悬浮按钮"
android:layout_marginTop="16dp" />
<!-- 添加状态显示文本 -->
<TextView
android:id="@+id/tvFloatingButtonStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="悬浮按钮状态: 未知"
android:layout_marginTop="8dp" />
</LinearLayout>