改进根访问处理和辅助功能

- 添加了新的 Kotlin 文件 `RootHelper.kt`,其中包含用于管理根权限的实用函数
- 更新了辅助服务配置,以包括新的事件类型和反馈类型
- 实现了 `TapIndicatorView` 类,用于在应用中显示点击指示器
This commit is contained in:
lvlisong 2025-03-24 19:16:58 +08:00
parent c52fc91bdd
commit e91a24f146
6 changed files with 149 additions and 79 deletions

View File

@ -6,6 +6,9 @@
<uses-permission android:name="android.permission.REORDER_TASKS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:allowBackup="true"

View File

@ -8,6 +8,7 @@ import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.WindowManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
@ -45,6 +46,14 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
if (RootHelper.requestRootAccess()) {
Toast.makeText(this, "Root 权限获取成功", Toast.LENGTH_LONG).show()
Log.d("MainActivity", "Root access granted")
} else {
Toast.makeText(this, "Root 权限获取失败", Toast.LENGTH_LONG).show()
Log.e("MainActivity", "Failed to obtain root access")
}
// 初始化 ViewModel
viewModel.onViewCreated() // 调用 ViewModel 的初始化方法

View File

@ -50,6 +50,8 @@ class MyAccessibilityService : AccessibilityService() {
private val isCheckAppPackageName = AtomicBoolean(false) // 防止重复执行
private val isCheckActivity = AtomicBoolean(false) // 防止重复执行
private lateinit var tapIndicatorView: TapIndicatorView
// 创建一个专门用于执行后台任务的 HandlerThread
private val backgroundHandlerThread = HandlerThread("RetryOperationThread").apply { start() }
@ -58,6 +60,7 @@ class MyAccessibilityService : AccessibilityService() {
override fun onServiceConnected() {
super.onServiceConnected()
tapIndicatorView = TapIndicatorView(this)
serviceInfo.apply {
flags = AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS
@ -103,41 +106,18 @@ class MyAccessibilityService : AccessibilityService() {
* 3. 校验节点可见性和可点击性
*/
fun simulateTap(x: Int, y: Int) {
var root: AccessibilityNodeInfo? = null
val tempRect = Rect()
Log.d(TAG, "Simulating tap at ($x, $y)")
val command = "input tap $x $y"
try {
root = rootInActiveWindow ?: run {
Log.e(TAG, "No root window available")
return
}
// **显示指示器**
tapIndicatorView.showTapIndicator(x, y)
val wrappedRoot = AccessibilityNodeInfoCompat.wrap(root)
try {
findNodesByCondition(wrappedRoot) { node ->
Log.d(
TAG,
"Checking node: [ID:${node.viewIdResourceName}], bounds: [${
node.getBoundsInScreen(tempRect)
}], clickable: ${node.isClickable}"
)
node.isClickable && node.isVisibleToUser() &&
node.run { getBoundsInScreen(tempRect); tempRect.contains(x, y) }
}.firstOrNull()?.apply {
if (performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
Log.d(TAG, "Tap performed at ($x, $y)")
} else {
Log.w(TAG, "Failed to perform click at ($x, $y)")
}
recycle()
} ?: Log.w(TAG, "No clickable node found at ($x, $y)")
} finally {
wrappedRoot.recycle()
}
} catch (e: Exception) {
Log.e(TAG, "Simulate tap error", e)
} finally {
root?.recycle()
if (RootHelper.runRootCommand(command)) {
Toast.makeText(this, "点击 ($x, $y) 成功", Toast.LENGTH_SHORT).show()
Log.d(TAG, "Executed: $command")
} else {
Toast.makeText(this, "点击 ($x, $y) 失败", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Failed to execute: $command")
}
}
@ -403,37 +383,6 @@ class MyAccessibilityService : AccessibilityService() {
}
}
// private inline fun safeRootOperation(block: (AccessibilityNodeInfoCompat) -> Unit) {
// val MAX_RETRIES = 5
// val INITIAL_DELAY = 1000L
// val BACKOFF_MULTIPLIER = 1.5f
//
// var currentDelay = INITIAL_DELAY
// repeat(MAX_RETRIES) { attempt ->
// try {
// val root = rootInActiveWindow?.let { AccessibilityNodeInfoCompat.wrap(it) }
// if (root != null) {
// // 使用 use 扩展自动回收资源
// block(root)
// return // 成功执行后退出
// }
//
// Log.w(TAG, "Window not ready (attempt ${attempt + 1}/$MAX_RETRIES)")
// if (attempt < MAX_RETRIES - 1) {
// Thread.sleep(currentDelay)
// currentDelay = (currentDelay * BACKOFF_MULTIPLIER).toLong()
// }
// } catch (e: Exception) {
// Log.e(TAG, "Attempt $attempt failed", e)
// if (e is InterruptedException) {
// Thread.currentThread().interrupt()
// return
// }
// }
// }
// Log.e(TAG, "Operation aborted after $MAX_RETRIES attempts")
// }
// region 节点操作工具方法
/**
* 递归查找符合条件的节点
@ -589,21 +538,15 @@ class MyAccessibilityService : AccessibilityService() {
Log.w(TAG, "Current class name is : $currentClassName")
try {
when (currentClassName) {
"io.sixminutes.breakingnews.MainActivity" -> launcherButtonText?.let {
findAndClickButton(
it
)
}
// "io.sixminutes.breakingnews.MainActivity" -> launcherButtonText?.let {
// findAndClickButton(it)
// }
"io.sixminutes.breakingnews.InterstitialActivity" -> Log.d(
TAG,
"current Activity is InterstitialActivity"
)
//"io.sixminutes.breakingnews.ClickTrackerActivity" -> simulateTap(300, 400)
//"com.applovin.adview.AppLovinFullscreenActivity" -> simulateTap(300, 400)
"com.applovin.adview.AppLovinFullscreenActivity" -> Log.d(
TAG,
"current Activity is AppLovinFullscreenActivity"
)
"io.sixminutes.breakingnews.ClickTrackerActivity" -> simulateTap(300, 400)
"com.applovin.adview.AppLovinFullscreenActivity" -> simulateTap(648, 62)
}
} finally {
isCheckActivity.set(false)

View File

@ -0,0 +1,57 @@
package io.sixminutes.ridicule
import android.util.Log
import java.io.DataOutputStream
object RootHelper {
private const val TAG = "RootHelper"
/**
* 检查设备是否已 Root
*/
fun isDeviceRooted(): Boolean {
return try {
Runtime.getRuntime().exec("su").outputStream.apply {
write("exit\n".toByteArray())
flush()
}
true
} catch (e: Exception) {
Log.e(TAG, "Root access not available", e)
false
}
}
/**
* 请求 Root 权限
*/
fun requestRootAccess(): Boolean {
return try {
val process = Runtime.getRuntime().exec("su")
val outputStream = DataOutputStream(process.outputStream)
outputStream.writeBytes("exit\n")
outputStream.flush()
process.waitFor() == 0
} catch (e: Exception) {
Log.e(TAG, "Failed to obtain root access", e)
false
}
}
/**
* 执行 Root Shell 命令
*/
fun runRootCommand(command: String): Boolean {
return try {
val process = Runtime.getRuntime().exec("su")
val outputStream = DataOutputStream(process.outputStream)
outputStream.writeBytes("$command\n")
outputStream.writeBytes("exit\n")
outputStream.flush()
process.waitFor() == 0
} catch (e: Exception) {
Log.e(TAG, "Root command execution failed: $command", e)
false
}
}
}

View File

@ -0,0 +1,55 @@
package io.sixminutes.ridicule
import android.content.Context
import android.graphics.Color
import android.graphics.PixelFormat
import android.os.Handler
import android.view.Gravity
import android.view.View
import android.view.WindowManager
class TapIndicatorView(private val context: Context) {
private var windowManager: WindowManager? = null
private var tapView: View? = null
/**
* 显示点击指示器
* @param x 点击位置 X 坐标
* @param y 点击位置 Y 坐标
*/
fun showTapIndicator(xx: Int, yy: Int) {
if (tapView != null) return
windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
// 创建一个小圆点作为指示器
tapView = View(context).apply {
setBackgroundColor(Color.RED) // 红色小点
alpha = 0.8f
}
val params = WindowManager.LayoutParams().apply {
width = 50 // 指示器大小
height = 50
x = xx - 25 // 居中对齐
y = yy - 25
gravity = Gravity.TOP or Gravity.START
format = PixelFormat.TRANSLUCENT
type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
}
windowManager?.addView(tapView, params)
// 5 秒后移除
Handler().postDelayed({ removeTapIndicator() }, 5000)
}
/**
* 移除点击指示器
*/
private fun removeTapIndicator() {
tapView?.let { windowManager?.removeView(it) }
tapView = null
}
}

View File

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackGeneric"
android:notificationTimeout="100"
android:accessibilityEventTypes="typeAllMask|typeViewClicked|typeViewFocused|typeNotificationStateChanged|typeWindowStateChanged"
android:accessibilityFeedbackType="feedbackAllMask|feedbackGeneric|feedbackSpoken"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews"
android:canRequestFilterKeyEvents="true"
android:notificationTimeout="100"
android:settingsActivity="io.sixminutes.ridicule.MainActivity"/>