This commit is contained in:
wdvipa
2026-02-11 16:59:49 +08:00
commit eee3a16150
3327 changed files with 198527 additions and 0 deletions

View File

@@ -0,0 +1,446 @@
package com.hikoncont.manager
import android.accessibilityservice.AccessibilityService
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.accessibility.AccessibilityNodeInfo
import com.hikoncont.service.AccessibilityRemoteService
/**
* 输入控制器
*
* 负责处理文本输入和系统按键操作
*/
class InputController(private val service: AccessibilityRemoteService) {
companion object {
private const val TAG = "InputController"
}
private val context: Context = service
private val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
/**
* 输入文本 - 智能输入策略
*/
fun inputText(text: String) {
try {
Log.d(TAG, "开始输入文本: $text")
// ✅ vivo设备特殊处理尝试键盘点击输入
if (isVivoDevice() && shouldUseKeyboardClick(text)) {
Log.d(TAG, "📱 vivo设备尝试键盘点击输入")
if (tryKeyboardClickInput(text)) {
Log.d(TAG, "✅ vivo键盘点击输入成功")
return
}
Log.w(TAG, "⚠️ vivo键盘点击失败回退到标准输入")
}
when {
// 优先尝试直接设置文本
tryDirectTextSetting(text) -> {
Log.d(TAG, "使用直接设置方式输入成功")
}
// 降级到剪贴板粘贴
tryClipboardPaste(text) -> {
Log.d(TAG, "使用剪贴板粘贴方式输入成功")
}
// 最后使用逐字符模拟输入
else -> {
simulateTyping(text)
Log.d(TAG, "使用模拟输入方式")
}
}
} catch (e: Exception) {
Log.e(TAG, "输入文本失败: $text", e)
}
}
/**
* ✅ 新增:追加字符到输入框 - 用于逐字符输入
*/
fun appendCharacter(char: String) {
try {
Log.d(TAG, "追加字符: '$char'")
when {
// 优先尝试追加字符到现有文本
tryAppendCharacter(char) -> {
Log.d(TAG, "使用追加方式输入字符成功: '$char'")
}
// 降级到剪贴板追加
tryClipboardAppend(char) -> {
Log.d(TAG, "使用剪贴板追加方式输入字符成功: '$char'")
}
// 最后使用光标位置插入
else -> {
tryCursorInsert(char)
Log.d(TAG, "使用光标插入方式输入字符: '$char'")
}
}
} catch (e: Exception) {
Log.e(TAG, "追加字符失败: '$char'", e)
}
}
/**
* 尝试直接设置文本
*/
private fun tryDirectTextSetting(text: String): Boolean {
return try {
val focusedNode = findFocusedInputNode()
focusedNode?.let { node ->
val bundle = Bundle().apply {
putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text)
}
val result = node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, bundle)
node.recycle()
result
} ?: false
} catch (e: Exception) {
Log.w(TAG, "直接设置文本失败", e)
false
}
}
/**
* 尝试剪贴板粘贴
*/
private fun tryClipboardPaste(text: String): Boolean {
return try {
// 1. 将文本复制到剪贴板
val clipData = ClipData.newPlainText("remote_input", text)
clipboardManager.setPrimaryClip(clipData)
// 2. 模拟粘贴操作
val focusedNode = findFocusedInputNode()
focusedNode?.let { node ->
val result = node.performAction(AccessibilityNodeInfo.ACTION_PASTE)
node.recycle()
result
} ?: false
} catch (e: Exception) {
Log.w(TAG, "剪贴板粘贴失败", e)
false
}
}
/**
* 模拟逐字符输入
*/
private fun simulateTyping(text: String) {
val focusedNode = findFocusedInputNode()
focusedNode?.let { node ->
try {
// 设置新文本
val bundle = Bundle().apply {
putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text)
}
node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, bundle)
} catch (e: Exception) {
Log.w(TAG, "模拟输入失败", e)
} finally {
node.recycle()
}
}
}
/**
* ✅ 新增:尝试追加字符到现有文本
*/
private fun tryAppendCharacter(char: String): Boolean {
return try {
val focusedNode = findFocusedInputNode()
focusedNode?.let { node ->
// 获取当前文本
val currentText = node.text?.toString() ?: ""
// 追加新字符
val newText = currentText + char
val bundle = Bundle().apply {
putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, newText)
}
val result = node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, bundle)
node.recycle()
result
} ?: false
} catch (e: Exception) {
Log.w(TAG, "追加字符失败", e)
false
}
}
/**
* ✅ 新增:尝试使用剪贴板追加字符
*/
private fun tryClipboardAppend(char: String): Boolean {
return try {
val focusedNode = findFocusedInputNode()
focusedNode?.let { node ->
// 获取当前文本
val currentText = node.text?.toString() ?: ""
// 追加新字符
val newText = currentText + char
// 将新文本复制到剪贴板
val clipData = ClipData.newPlainText("remote_append", newText)
clipboardManager.setPrimaryClip(clipData)
// 先清空再粘贴
val clearBundle = Bundle().apply {
putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "")
}
node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, clearBundle)
// 粘贴新内容
val result = node.performAction(AccessibilityNodeInfo.ACTION_PASTE)
node.recycle()
result
} ?: false
} catch (e: Exception) {
Log.w(TAG, "剪贴板追加失败", e)
false
}
}
/**
* ✅ 新增:尝试在光标位置插入字符
*/
private fun tryCursorInsert(char: String) {
val focusedNode = findFocusedInputNode()
focusedNode?.let { node ->
try {
// 获取当前文本和光标位置
val currentText = node.text?.toString() ?: ""
// 如果无法获取光标位置,默认追加到末尾
val newText = currentText + char
val bundle = Bundle().apply {
putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, newText)
}
node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, bundle)
} catch (e: Exception) {
Log.w(TAG, "光标插入失败", e)
} finally {
node.recycle()
}
}
}
/**
* 查找当前聚焦的输入节点
*/
private fun findFocusedInputNode(): AccessibilityNodeInfo? {
return try {
val rootNode = service.rootInActiveWindow ?: return null
val focusedNode = rootNode.findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
// 如果找不到输入焦点,尝试查找可编辑的节点
if (focusedNode == null) {
findEditableNode(rootNode)
} else {
focusedNode
}
} catch (e: Exception) {
Log.e(TAG, "查找输入节点失败", e)
null
}
}
/**
* 查找可编辑的节点
*/
private fun findEditableNode(rootNode: AccessibilityNodeInfo): AccessibilityNodeInfo? {
if (rootNode.isEditable && rootNode.isFocusable) {
return rootNode
}
for (i in 0 until rootNode.childCount) {
val child = rootNode.getChild(i) ?: continue
val result = findEditableNode(child)
if (result != null) {
child.recycle()
return result
}
child.recycle()
}
return null
}
/**
* 执行返回键
*/
fun performBack() {
try {
service.performGlobalActionWithLog(AccessibilityService.GLOBAL_ACTION_BACK)
Log.d(TAG, "执行返回键")
} catch (e: Exception) {
Log.e(TAG, "执行返回键失败", e)
}
}
/**
* 执行Home键
*/
fun performHome() {
try {
service.performGlobalActionWithLog(AccessibilityService.GLOBAL_ACTION_HOME)
Log.d(TAG, "执行Home键")
} catch (e: Exception) {
Log.e(TAG, "执行Home键失败", e)
}
}
/**
* 执行最近任务键
*/
fun performRecents() {
try {
service.performGlobalActionWithLog(AccessibilityService.GLOBAL_ACTION_RECENTS)
Log.d(TAG, "执行最近任务键")
} catch (e: Exception) {
Log.e(TAG, "执行最近任务键失败", e)
}
}
/**
* 执行通知栏操作
*/
fun performNotifications() {
try {
service.performGlobalActionWithLog(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS)
Log.d(TAG, "打开通知栏")
} catch (e: Exception) {
Log.e(TAG, "打开通知栏失败", e)
}
}
/**
* 执行快速设置
*/
fun performQuickSettings() {
try {
service.performGlobalActionWithLog(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS)
Log.d(TAG, "打开快速设置")
} catch (e: Exception) {
Log.e(TAG, "打开快速设置失败", e)
}
}
/**
* 清空输入框
*/
fun clearInput() {
try {
val focusedNode = findFocusedInputNode()
focusedNode?.let { node ->
val bundle = Bundle().apply {
putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "")
}
node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, bundle)
node.recycle()
}
Log.d(TAG, "清空输入框")
} catch (e: Exception) {
Log.e(TAG, "清空输入框失败", e)
}
}
/**
* 获取当前输入框的文本
*/
fun getCurrentInputText(): String {
return try {
val focusedNode = findFocusedInputNode()
val text = focusedNode?.text?.toString() ?: ""
focusedNode?.recycle()
text
} catch (e: Exception) {
Log.e(TAG, "获取输入框文本失败", e)
""
}
}
// ✅ vivo设备支持方法
private fun isVivoDevice(): Boolean {
val brand = android.os.Build.BRAND?.lowercase() ?: ""
val manufacturer = android.os.Build.MANUFACTURER?.lowercase() ?: ""
return brand.contains("vivo") || manufacturer.contains("vivo") || brand.contains("iqoo")
}
private fun shouldUseKeyboardClick(text: String): Boolean {
// 只对包含字母的混合密码或文本使用键盘点击
return text.any { it.isLetter() } && text.length > 1
}
private fun tryKeyboardClickInput(text: String): Boolean {
return try {
val rootNode = service.rootInActiveWindow ?: return false
val buttons = findKeyboardButtons(rootNode)
if (buttons.isEmpty()) return false
text.forEach { char ->
val button = buttons.find {
it.text.equals(char.toString(), ignoreCase = true)
}
if (button != null) {
performClick(button.x, button.y)
Thread.sleep(200)
} else {
return false
}
}
true
} catch (e: Exception) {
Log.e(TAG, "键盘点击输入失败", e)
false
}
}
private fun findKeyboardButtons(rootNode: AccessibilityNodeInfo): List<KeyButton> {
val buttons = mutableListOf<KeyButton>()
fun scan(node: AccessibilityNodeInfo) {
if (node.isClickable) {
val text = (node.text ?: node.contentDescription)?.toString() ?: ""
if (text.length == 1 && (text[0].isLetterOrDigit() || text[0].isWhitespace())) {
val bounds = android.graphics.Rect()
node.getBoundsInScreen(bounds)
buttons.add(KeyButton(text, bounds.centerX().toFloat(), bounds.centerY().toFloat()))
}
}
for (i in 0 until node.childCount) {
node.getChild(i)?.let { scan(it); it.recycle() }
}
}
scan(rootNode)
return buttons
}
private fun performClick(x: Float, y: Float) {
val path = android.graphics.Path().apply {
moveTo(x, y)
}
val gesture = android.accessibilityservice.GestureDescription.Builder()
.addStroke(android.accessibilityservice.GestureDescription.StrokeDescription(path, 0, 100))
.build()
service.dispatchGesture(gesture, null, null)
}
private data class KeyButton(val text: String, val x: Float, val y: Float)
}