refactor: ###

优化代码并更新文件中的UI元素

- 重构了MainViewModel.kt中的代码,去除了未使用的导入项和不必要的初始化
- 更新了floating_window.xml布局中的样式
- 在AndroidManifest.xml中添加了PACKAGE_USAGE_STATS权限
This commit is contained in:
lvlisong 2025-03-26 00:26:20 +08:00
parent 5dcda8829d
commit 054e86ec13
4 changed files with 119 additions and 116 deletions

View File

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

View File

@ -6,8 +6,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.provider.Settings import android.provider.Settings
import android.util.Log import android.util.Log
import android.view.View
import android.view.WindowManager
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -15,7 +13,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.lang.ref.WeakReference
class MainViewModel(application: Application) : AndroidViewModel(application) { class MainViewModel(application: Application) : AndroidViewModel(application) {
@ -26,9 +23,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
// region 依赖项 // region 依赖项
private val gaidHelper by lazy { GAIDHelper(application) } private val gaidHelper by lazy { GAIDHelper(application) }
private val windowManager by lazy {
getApplication<Application>().getSystemService(Context.WINDOW_SERVICE) as WindowManager
}
// endregion // endregion
// region 公共API // region 公共API
@ -58,9 +52,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
delay(1000) // 延迟 1000 毫秒,确保桌面已经显示 delay(1000) // 延迟 1000 毫秒,确保桌面已经显示
try { try {
MyAccessibilityService.getInstance()?.findAndLaunchApp( MyAccessibilityService.getInstance()?.findAndLaunchApp(
"breakingnews", "breakingnews", "io.sixminutes.breakingnews", "Test"
"io.sixminutes.breakingnews",
"Test"
//"Show Inter Ad 1: 3689d2816239b64e" //"Show Inter Ad 1: 3689d2816239b64e"
) )
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -50,7 +50,7 @@ class MyAccessibilityService : AccessibilityService() {
private var targetAppName: String? = null private var targetAppName: String? = null
private var targetAppPackageName: String? = null private var targetAppPackageName: String? = null
private var currentAppPackageName: String? = null private var currentAppPackageName: String? = null
private var currentClassName: String? = null private var currentActivityName: String? = null
private var launcherButtonText: String? = null private var launcherButtonText: String? = null
private val mainHandler by lazy { Handler(Looper.getMainLooper()) } private val mainHandler by lazy { Handler(Looper.getMainLooper()) }
private var running = false private var running = false
@ -119,13 +119,13 @@ class MyAccessibilityService : AccessibilityService() {
// **显示指示器** // **显示指示器**
tapIndicatorView.showTapIndicator(x, y) tapIndicatorView.showTapIndicator(x, y)
if (RootHelper.runRootCommand(command)) { // if (RootHelper.runRootCommand(command)) {
Toast.makeText(this, "点击 ($x, $y) 成功", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "点击 ($x, $y) 成功", Toast.LENGTH_SHORT).show()
Log.d(TAG, "Executed: $command") // Log.d(TAG, "Executed: $command")
} else { // } else {
Toast.makeText(this, "点击 ($x, $y) 失败", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "点击 ($x, $y) 失败", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Failed to execute: $command") // Log.e(TAG, "Failed to execute: $command")
} // }
} }
/** /**
@ -140,6 +140,8 @@ class MyAccessibilityService : AccessibilityService() {
buttonText: String? = null, buttonText: String? = null,
): Boolean { ): Boolean {
var success = false var success = false
// 显示悬浮窗
createFloatingWindow()
safeRootOperation { root -> safeRootOperation { root ->
findNodesByCondition(root) { node -> findNodesByCondition(root) { node ->
node.textMatches(appName) || node.contentDescriptionMatches(appName) node.textMatches(appName) || node.contentDescriptionMatches(appName)
@ -150,32 +152,18 @@ class MyAccessibilityService : AccessibilityService() {
launcherButtonText = buttonText launcherButtonText = buttonText
targetAppName = appName targetAppName = appName
success = true success = true
// 显示悬浮窗
createFloatingWindow()
} else { } else {
Log.w(TAG, "Failed to click app: $appName") Log.w(TAG, "Failed to click app: $appName")
showToast("Failed to click app: $appName") updateFloatingText("Failed to click app: $appName", true)
} }
} ?: { } ?: {
Log.w(TAG, "App not found: $appName") Log.w(TAG, "App not found: $appName")
showToast("App not found: $appName") updateFloatingText("App not found: $appName", true)
} }
} }
return success return success
} }
private fun isTargetAppRunning(): Boolean {
val currentRoot = rootInActiveWindow
return if (currentRoot != null) {
currentAppPackageName = currentRoot.packageName?.toString()
currentAppPackageName == targetAppPackageName
} else {
Log.e(TAG, "isTargetAppRunning: rootInActiveWindow is null")
false
}
}
// region 点击逻辑优化 // region 点击逻辑优化
/** /**
* 智能点击方案综合节点属性/父容器/坐标点击 * 智能点击方案综合节点属性/父容器/坐标点击
@ -290,6 +278,7 @@ class MyAccessibilityService : AccessibilityService() {
retryInterval: Long = DEFAULT_RETRY_INTERVAL retryInterval: Long = DEFAULT_RETRY_INTERVAL
): Boolean { ): Boolean {
Log.d(TAG, "Attempting to click button: '$buttonText'") Log.d(TAG, "Attempting to click button: '$buttonText'")
updateFloatingText("Attempting to click button: '$buttonText'")
var success = false var success = false
safeRootOperation { root -> safeRootOperation { root ->
@ -338,8 +327,7 @@ class MyAccessibilityService : AccessibilityService() {
var retryCount = 0 var retryCount = 0
fun attempt() { fun attempt() {
if (!running) if (!running) return
return
when { when {
checkCondition() -> onSuccess() checkCondition() -> onSuccess()
retryCount < maxRetries -> { retryCount < maxRetries -> {
@ -454,75 +442,95 @@ class MyAccessibilityService : AccessibilityService() {
} }
} }
// 检查当前窗口是哪个
private fun isTargetAppRunning(): Boolean {
val currentRoot = rootInActiveWindow
return if (currentRoot != null) {
currentAppPackageName = currentRoot.packageName?.toString()
currentAppPackageName == targetAppPackageName
} else {
Log.e(TAG, "isTargetAppRunning: rootInActiveWindow is null")
updateFloatingText("rootInActiveWindow is null", true)
false
}
}
private fun handleWindowChange(event: AccessibilityEvent) { private fun handleWindowChange(event: AccessibilityEvent) {
if (!running) return if (!running) return
if (targetAppPackageName == null) return
currentAppPackageName = event.packageName?.toString() ?: return val packageName = event.packageName?.toString() ?: "unknownPackageName"
currentClassName = event.className?.toString() ?: return val activity = event.className?.toString() ?: "unknownActivity"
if (packageName == "io.sixminutes.ridicule" && activity == "android.widget.LinearLayout") {
Log.w(TAG, "Detected current app is floating window")
return
}
// 增强日志输出 // 增强日志输出
Log.d( Log.d(
TAG, """ TAG, """
Window Changed: Window Changed:
Current Package : $currentAppPackageName Current Package : $packageName
Target Package : $targetAppPackageName Target Package : $targetAppPackageName
Class Name : ${currentClassName?.substringAfterLast('.')} Class Name : ${activity.substringAfterLast('.')}
Full Class Name : $currentClassName Full Class Name : $activity
""".trimIndent() """.trimIndent()
) )
if (targetAppPackageName == null) return
currentAppPackageName = packageName
// 当检测到切换到其他应用时 // 当检测到切换到其他应用时
if (currentAppPackageName != targetAppPackageName) { if (currentAppPackageName != targetAppPackageName) {
checkAppPackageName()
// 先检查 isCheckAppPackageName如果为 true 则不重复启动
if (!isCheckAppPackageName) {
isCheckAppPackageName = true
mainHandler.postDelayed(::checkAppPackageName, DEFAULT_RETRY_INTERVAL)
} else { } else {
Log.w(TAG, "CheckAppPackageName operation is already running, skipping...") currentActivityName = activity
checkActivity()
} }
} else {
// 先检查 isCheckActivity如果为 true 则不重复启动
if (!isCheckActivity) {
isCheckActivity = true
mainHandler.postDelayed(::checkActivity, DEFAULT_RETRY_INTERVAL)
} else {
Log.w(TAG, "CheckActivity operation is already running, skipping...")
}
}
Log.d(
TAG,
"Current App Package: $currentAppPackageName; Current App Class: $currentClassName; targetAppPackageName: $targetAppPackageName"
)
} }
// 检查前端应用 // 检查前端应用
private fun checkAppPackageName() { private fun checkAppPackageName() {
if (isCheckAppPackageName) {
Log.w(TAG, "CheckAppPackageName operation is already running, skipping...")
updateFloatingText("CheckAppPackageName operation is already running, skipping...")
return
}
isCheckAppPackageName = true
Log.w(TAG, "Detected switch to non-target app: $currentAppPackageName") Log.w(TAG, "Detected switch to non-target app: $currentAppPackageName")
try { updateFloatingText("Detected switch to non-target app: $currentAppPackageName")
retryOperation(DEFAULT_MAX_RETRIES, DEFAULT_RETRY_INTERVAL, checkCondition = { retryOperation(DEFAULT_MAX_RETRIES, DEFAULT_RETRY_INTERVAL, checkCondition = {
val ok = isTargetAppRunning() val ok = isTargetAppRunning()
ok || performReturnGlobalAction() ok || performReturnGlobalAction()
Log.d(TAG, "Check $targetAppPackageName is activated: $ok") Log.d(TAG, "Check $targetAppPackageName is activated: $ok")
ok ok
}, onSuccess = { }, onSuccess = {
isCheckAppPackageName = false // 任务结束后重置
updateFloatingText("Successfully returned to target app")
Log.i(TAG, "Successfully returned to target app") Log.i(TAG, "Successfully returned to target app")
// 返回后再次检查 Activity
mainHandler.postDelayed(::checkActivity, DEFAULT_RETRY_INTERVAL)
}, onFailure = { }, onFailure = {
isCheckAppPackageName = false // 任务结束后重置
updateFloatingText("Failed to return after $DEFAULT_MAX_RETRIES attempts", true)
Log.e(TAG, "Failed to return after $DEFAULT_MAX_RETRIES attempts") Log.e(TAG, "Failed to return after $DEFAULT_MAX_RETRIES attempts")
returnToHomeAndRestart() returnToHomeAndRestart()
}) })
} finally {
isCheckAppPackageName = false // 任务结束后重置
}
} }
// 检查当前 Activity // 检查当前 Activity
private fun checkActivity() { private fun checkActivity() {
Log.w(TAG, "Current class name is : $currentClassName") if (isCheckActivity) {
Log.w(TAG, "CheckActivity operation is already running, skipping...")
updateFloatingText("CheckActivity operation is already running, skipping...")
return
}
isCheckActivity = true
Log.w(TAG, "Current class name is : $currentActivityName")
try { try {
when (currentClassName) { when (currentActivityName) {
"io.sixminutes.breakingnews.MainActivity" -> launcherButtonText?.let { "io.sixminutes.breakingnews.MainActivity" -> launcherButtonText?.let {
findAndClickButton(it) findAndClickButton(it)
} }
@ -538,6 +546,13 @@ class MyAccessibilityService : AccessibilityService() {
) )
"com.applovin.adview.AppLovinFullscreenActivity" -> simulateTap(648, 62) "com.applovin.adview.AppLovinFullscreenActivity" -> simulateTap(648, 62)
else -> {
Log.d(TAG, "current Activity is unknown")
updateFloatingText("current Activity is unknown")
mainHandler.postDelayed({
checkActivity()
}, DEFAULT_RETRY_INTERVAL)
}
} }
} finally { } finally {
isCheckActivity = false isCheckActivity = false
@ -564,8 +579,9 @@ class MyAccessibilityService : AccessibilityService() {
*/ */
private fun returnToHomeAndRestart(): Boolean { private fun returnToHomeAndRestart(): Boolean {
Log.d(TAG, "Used home+restart strategy") Log.d(TAG, "Used home+restart strategy")
updateFloatingText("used home+restart strategy")
return performGlobalAction(GLOBAL_ACTION_HOME) && targetAppName != null && targetAppPackageName != null && findAndLaunchApp( return performGlobalAction(GLOBAL_ACTION_HOME) && targetAppName != null && targetAppPackageName != null && findAndLaunchApp(
targetAppName!!, targetAppPackageName!! targetAppName!!, targetAppPackageName!!, launcherButtonText
) )
} }
@ -602,7 +618,7 @@ class MyAccessibilityService : AccessibilityService() {
// 设置悬浮窗口显示在屏幕的左上角x,y 为偏移量 // 设置悬浮窗口显示在屏幕的左上角x,y 为偏移量
layoutParams.gravity = android.view.Gravity.BOTTOM or android.view.Gravity.END layoutParams.gravity = android.view.Gravity.BOTTOM or android.view.Gravity.END
layoutParams.x = 0 layoutParams.x = 0
layoutParams.y = 100 layoutParams.y = 150
// 将悬浮窗口添加到 WindowManager 中显示 // 将悬浮窗口添加到 WindowManager 中显示
windowManager?.addView(floatingView, layoutParams) windowManager?.addView(floatingView, layoutParams)
@ -636,10 +652,17 @@ class MyAccessibilityService : AccessibilityService() {
* 更新悬浮窗口中显示的文本 * 更新悬浮窗口中显示的文本
* @param text 要显示的文本内容 * @param text 要显示的文本内容
*/ */
private fun updateFloatingText(text: String) { private fun updateFloatingText(text: String, isError: Boolean = false) {
// 在悬浮窗口中找到 TextView 并设置新文本 // 在悬浮窗口中找到 TextView 并设置新文本
val textView = floatingView?.findViewById<TextView>(R.id.first_message) if (isError) {
textView?.text = text val errorView = floatingView?.findViewById<TextView>(R.id.error_message)
errorView?.text = text
} else {
val info1 = floatingView?.findViewById<TextView>(R.id.info_message_1)
val info2 = floatingView?.findViewById<TextView>(R.id.info_message_2)
info2?.text = info1?.text
info1?.text = text
}
} }
/** /**

View File

@ -1,49 +1,36 @@
<!-- res/layout/floating_window.xml --> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameLayout2" android:id="@+id/frameLayout2"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:background="#22000080"
android:background="#22000000"> android:padding="8dp"
android:orientation="vertical">
<TextView <TextView
android:id="@+id/first_message" android:id="@+id/info_message_2"
android:layout_width="240ddp" android:layout_width="240dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="2dp" android:padding="2dp"
android:text="2" android:text=""
android:textSize="18sp" android:textSize="12sp"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/first_second" android:id="@+id/info_message_1"
android:layout_width="240pdp" android:layout_width="240dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="2dp" android:padding="2dp"
android:text="1" android:text=""
android:textSize="18sp" android:textSize="12sp"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<TextView <TextView
android:id="@+id/main_message" android:id="@+id/error_message"
android:layout_width="240pdp" android:layout_width="240dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="2dp" android:padding="2dp"
android:text="xxxx" android:text=""
android:textSize="18sp" android:textColor="#ff0000"
app:layout_constraintBottom_toTopOf="@+id/first_message" android:textSize="14sp"/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>