测试
This commit is contained in:
446
app/src/main/java/com/hikoncont/manager/InputController.kt
Normal file
446
app/src/main/java/com/hikoncont/manager/InputController.kt
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user