填充表单
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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的完整资源名称
|
||||
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
54
app/src/main/java/com/loveerror/bested/MyApplication.kt
Normal file
54
app/src/main/java/com/loveerror/bested/MyApplication.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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("未找到集团等级选项或选项不可点击")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
202
app/src/main/java/com/loveerror/bested/handler/ZJMCCMCRM.kt
Normal file
202
app/src/main/java/com/loveerror/bested/handler/ZJMCCMCRM.kt
Normal 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,"普通建档","集团状态","证件名称")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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普通建档")
|
||||
}
|
||||
}
|
||||
49
app/src/main/java/com/loveerror/bested/task/TaskManager.kt
Normal file
49
app/src/main/java/com/loveerror/bested/task/TaskManager.kt
Normal 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) {
|
||||
// 异常处理
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
228
app/src/main/java/com/loveerror/bested/tool/AccessibilityTool.kt
Normal file
228
app/src/main/java/com/loveerror/bested/tool/AccessibilityTool.kt
Normal 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的完整资源名称
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
54
app/src/main/java/com/loveerror/bested/tool/NodeToolHint.kt
Normal file
54
app/src/main/java/com/loveerror/bested/tool/NodeToolHint.kt
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
96
app/src/main/java/com/loveerror/bested/tool/NodeToolText.kt
Normal file
96
app/src/main/java/com/loveerror/bested/tool/NodeToolText.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
72
app/src/main/java/com/loveerror/bested/tool/WebViewTool.kt
Normal file
72
app/src/main/java/com/loveerror/bested/tool/WebViewTool.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user