改进根访问处理和辅助功能
- 添加了新的 Kotlin 文件 `RootHelper.kt`,其中包含用于管理根权限的实用函数 - 更新了辅助服务配置,以包括新的事件类型和反馈类型 - 实现了 `TapIndicatorView` 类,用于在应用中显示点击指示器
This commit is contained in:
parent
c52fc91bdd
commit
e91a24f146
|
@ -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"
|
||||
|
|
|
@ -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 的初始化方法
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
57
app/src/main/java/io/sixminutes/ridicule/RootHelper.kt
Normal file
57
app/src/main/java/io/sixminutes/ridicule/RootHelper.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
55
app/src/main/java/io/sixminutes/ridicule/TapIndicatorView.kt
Normal file
55
app/src/main/java/io/sixminutes/ridicule/TapIndicatorView.kt
Normal 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
|
||||
}
|
||||
}
|
|
@ -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"/>
|
Loading…
Reference in New Issue
Block a user